github.com/trustbloc/kms-go@v1.1.2/kms/localkms/crypto_box.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package localkms
     8  
     9  import (
    10  	"bytes"
    11  	"crypto/ed25519"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  
    16  	"github.com/golang/protobuf/proto"
    17  	"github.com/google/tink/go/aead"
    18  	"github.com/google/tink/go/keyset"
    19  	ed25519pb "github.com/google/tink/go/proto/ed25519_go_proto"
    20  	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
    21  	"golang.org/x/crypto/nacl/box"
    22  
    23  	"github.com/trustbloc/kms-go/doc/util/jwkkid"
    24  	"github.com/trustbloc/kms-go/kms/localkms/internal/keywrapper"
    25  	"github.com/trustbloc/kms-go/secretlock/noop"
    26  	"github.com/trustbloc/kms-go/util/cryptoutil"
    27  
    28  	"github.com/trustbloc/kms-go/spi/kms"
    29  )
    30  
    31  // TODO: move CryptoBox out of the KMS package.
    32  //   this currently only sits inside LocalKMS so it can access private keys. See issue #511
    33  // TODO delete this file and its corresponding test file when LegacyPacker is removed.
    34  
    35  // CryptoBox provides an elliptic-curve-based authenticated encryption scheme
    36  //
    37  // Payloads are encrypted using symmetric encryption (XChacha20Poly1305)
    38  // using a shared key derived from a shared secret created by
    39  //
    40  //	Curve25519 Elliptic Curve Diffie-Hellman key exchange.
    41  //
    42  // CryptoBox is created by a KMS, and reads secret keys from the KMS
    43  //
    44  //	for encryption/decryption, so clients do not need to see
    45  //	the secrets themselves.
    46  type CryptoBox struct {
    47  	km *LocalKMS
    48  }
    49  
    50  // NewCryptoBox creates a CryptoBox which provides crypto box encryption using the given KMS's key.
    51  func NewCryptoBox(w kms.KeyManager) (*CryptoBox, error) {
    52  	lkms, ok := w.(*LocalKMS)
    53  	if !ok {
    54  		return nil, fmt.Errorf("cannot use parameter argument as KMS")
    55  	}
    56  
    57  	return &CryptoBox{km: lkms}, nil
    58  }
    59  
    60  // Easy seals a message with a provided nonce
    61  // theirPub is used as a public key, while myPub is used to identify the private key that should be used.
    62  func (b *CryptoBox) Easy(payload, nonce, theirPub []byte, myKID string) ([]byte, error) {
    63  	var recPubBytes [cryptoutil.Curve25519KeySize]byte
    64  
    65  	copy(recPubBytes[:], theirPub)
    66  
    67  	senderPriv, err := b.km.exportEncPrivKeyBytes(myKID)
    68  	if err != nil {
    69  		return nil, fmt.Errorf("easy: failed to export sender key: %w, kid: %v", err, myKID)
    70  	}
    71  
    72  	var (
    73  		priv       [cryptoutil.Curve25519KeySize]byte
    74  		nonceBytes [cryptoutil.NonceSize]byte
    75  	)
    76  
    77  	copy(priv[:], senderPriv)
    78  	copy(nonceBytes[:], nonce)
    79  
    80  	ret := box.Seal(nil, payload, &nonceBytes, &recPubBytes, &priv)
    81  
    82  	return ret, nil
    83  }
    84  
    85  // EasyOpen unseals a message sealed with Easy, where the nonce is provided
    86  // theirPub is the public key used to decrypt directly, while myPub is used to identify the private key to be used.
    87  func (b *CryptoBox) EasyOpen(cipherText, nonce, theirPub, myPub []byte) ([]byte, error) {
    88  	//	 myPub is used to get the recipient private key for decryption
    89  	var sendPubBytes [cryptoutil.Curve25519KeySize]byte
    90  
    91  	copy(sendPubBytes[:], theirPub)
    92  
    93  	kid, err := jwkkid.CreateKID(myPub, kms.ED25519Type)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	senderPriv, err := b.km.exportEncPrivKeyBytes(kid)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	var (
   104  		priv       [cryptoutil.Curve25519KeySize]byte
   105  		nonceBytes [cryptoutil.NonceSize]byte
   106  	)
   107  
   108  	copy(priv[:], senderPriv)
   109  	copy(nonceBytes[:], nonce)
   110  
   111  	out, success := box.Open(nil, cipherText, &nonceBytes, &sendPubBytes, &priv)
   112  	if !success {
   113  		return nil, errors.New("failed to unpack")
   114  	}
   115  
   116  	return out, nil
   117  }
   118  
   119  // Seal seals a payload using the equivalent of libsodium box_seal
   120  //
   121  // Generates an ephemeral keypair to use for the sender, and includes
   122  // the ephemeral sender public key in the message.
   123  func (b *CryptoBox) Seal(payload, theirEncPub []byte, randSource io.Reader) ([]byte, error) {
   124  	// generate ephemeral curve25519 asymmetric keys
   125  	epk, esk, err := box.GenerateKey(randSource)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	var recPubBytes [cryptoutil.Curve25519KeySize]byte
   131  
   132  	copy(recPubBytes[:], theirEncPub)
   133  
   134  	nonce, err := cryptoutil.Nonce(epk[:], theirEncPub)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	// now seal the msg with the ephemeral key, nonce and recPub (which is recipient's publicKey)
   140  	ret := box.Seal(epk[:], payload, nonce, &recPubBytes, esk)
   141  
   142  	return ret, nil
   143  }
   144  
   145  // SealOpen decrypts a payload encrypted with Seal
   146  //
   147  // Reads the ephemeral sender public key, prepended to a properly-formatted message,
   148  // and uses that along with the recipient private key corresponding to myPub to decrypt the message.
   149  func (b *CryptoBox) SealOpen(cipherText, myPub []byte) ([]byte, error) {
   150  	if len(cipherText) < cryptoutil.Curve25519KeySize {
   151  		return nil, errors.New("message too short")
   152  	}
   153  
   154  	kid, err := jwkkid.CreateKID(myPub, kms.ED25519Type)
   155  	if err != nil {
   156  		return nil, fmt.Errorf("sealOpen: failed to compute ED25519 kid: %w", err)
   157  	}
   158  
   159  	recipientEncPriv, err := b.km.exportEncPrivKeyBytes(kid)
   160  	if err != nil {
   161  		return nil, fmt.Errorf("sealOpen: failed to exportPriveKeyBytes: %w", err)
   162  	}
   163  
   164  	var (
   165  		epk  [cryptoutil.Curve25519KeySize]byte
   166  		priv [cryptoutil.Curve25519KeySize]byte
   167  	)
   168  
   169  	copy(epk[:], cipherText[:cryptoutil.Curve25519KeySize])
   170  	copy(priv[:], recipientEncPriv)
   171  
   172  	recEncPub, err := cryptoutil.PublicEd25519toCurve25519(myPub)
   173  	if err != nil {
   174  		return nil, fmt.Errorf("sealOpen: failed to convert pub Ed25519 to X25519 key: %w", err)
   175  	}
   176  
   177  	nonce, err := cryptoutil.Nonce(epk[:], recEncPub)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	out, success := box.Open(nil, cipherText[cryptoutil.Curve25519KeySize:], nonce, &epk, &priv)
   183  	if !success {
   184  		return nil, errors.New("failed to unpack")
   185  	}
   186  
   187  	return out, nil
   188  }
   189  
   190  // exportEncPrivKeyBytes temporary support function for crypto_box to be used with legacyPacker only.
   191  func (l *LocalKMS) exportEncPrivKeyBytes(id string) ([]byte, error) {
   192  	kh, err := l.getKeySet(id)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	buf := new(bytes.Buffer)
   198  	bWriter := keyset.NewBinaryWriter(buf)
   199  
   200  	kw, err := keywrapper.New(&noop.NoLock{}, "local-lock://tmp")
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	primaryKeyEnvAEAD := aead.NewKMSEnvelopeAEAD2(aead.AES256GCMKeyTemplate(), kw)
   206  
   207  	err = kh.Write(bWriter, primaryKeyEnvAEAD)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	encryptedKS := &tinkpb.EncryptedKeyset{}
   213  
   214  	err = proto.Unmarshal(buf.Bytes(), encryptedKS)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	decryptedKS, err := primaryKeyEnvAEAD.Decrypt(encryptedKS.EncryptedKeyset, []byte{})
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	return extractPrivKey(decryptedKS)
   225  }
   226  
   227  func extractPrivKey(marshalledKeySet []byte) ([]byte, error) {
   228  	ks := &tinkpb.Keyset{}
   229  
   230  	err := proto.Unmarshal(marshalledKeySet, ks)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	for _, key := range ks.Key {
   236  		if key.KeyId != ks.PrimaryKeyId || key.Status != tinkpb.KeyStatusType_ENABLED {
   237  			continue
   238  		}
   239  
   240  		prvKey := &ed25519pb.Ed25519PrivateKey{}
   241  
   242  		err = proto.Unmarshal(key.KeyData.Value, prvKey)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  
   247  		pkBytes := make([]byte, ed25519.PrivateKeySize)
   248  		copy(pkBytes[:ed25519.PublicKeySize], prvKey.KeyValue)
   249  		copy(pkBytes[ed25519.PublicKeySize:], prvKey.PublicKey.KeyValue)
   250  
   251  		return cryptoutil.SecretEd25519toCurve25519(pkBytes)
   252  	}
   253  
   254  	return nil, errors.New("private key not found")
   255  }