github.com/trustbloc/kms-go@v1.1.2/crypto/tinkcrypto/primitive/composite/keyio/composite_key_export.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package keyio
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"strings"
    16  
    17  	"github.com/golang/protobuf/proto"
    18  	tinkaead "github.com/google/tink/go/aead"
    19  	hybrid "github.com/google/tink/go/hybrid/subtle"
    20  	"github.com/google/tink/go/insecurecleartextkeyset"
    21  	"github.com/google/tink/go/keyset"
    22  	commonpb "github.com/google/tink/go/proto/common_go_proto"
    23  	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
    24  
    25  	"github.com/trustbloc/kms-go/spi/kms"
    26  
    27  	cryptoapi "github.com/trustbloc/kms-go/spi/crypto"
    28  
    29  	"github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/aead"
    30  	"github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/composite/ecdh"
    31  	ecdhpb "github.com/trustbloc/kms-go/crypto/tinkcrypto/primitive/proto/ecdh_aead_go_proto"
    32  )
    33  
    34  // Package keyio supports exporting of Composite keys (aka Write) and converting the public key part of the a composite
    35  // key (aka PublicKeyToHandle to be used as a valid Tink key)
    36  
    37  const (
    38  	nistPECDHKWPublicKeyTypeURL   = "type.hyperledger.org/hyperledger.aries.crypto.tink.NistPEcdhKwPublicKey"
    39  	x25519ECDHKWPublicKeyTypeURL  = "type.hyperledger.org/hyperledger.aries.crypto.tink.X25519EcdhKwPublicKey"
    40  	nistPECDHKWPrivateKeyTypeURL  = "type.hyperledger.org/hyperledger.aries.crypto.tink.NistPEcdhKwPrivateKey"
    41  	x25519ECDHKWPrivateKeyTypeURL = "type.hyperledger.org/hyperledger.aries.crypto.tink.X25519EcdhKwPrivateKey"
    42  )
    43  
    44  //nolint:gochecknoglobals
    45  var ecdhKeyTypes = map[string]kms.KeyType{
    46  	"NIST_P256": kms.NISTP256ECDHKWType,
    47  	"NIST_P384": kms.NISTP384ECDHKWType,
    48  	"NIST_P521": kms.NISTP521ECDHKWType,
    49  }
    50  
    51  // PubKeyWriter will write the raw bytes of a Tink KeySet's primary public key. The raw bytes are a marshaled
    52  // composite.VerificationMethod type.
    53  // The keyset must have a keyURL value equal to either one of the public key URLs:
    54  //   - `nistPECDHKWPublicKeyTypeURL`
    55  //   - `x25519ECDHKWPublicKeyTypeURL`
    56  //
    57  // constants of ecdh package.
    58  // Note: This writer should be used only for ECDH public key exports. Other export of public keys should be
    59  //
    60  //	called via localkms package.
    61  type PubKeyWriter struct {
    62  	// KeyType is Key Type of the written key. It's needed as Write() is an interface function and can't return it.
    63  	KeyType kms.KeyType
    64  	w       io.Writer
    65  }
    66  
    67  // NewWriter creates a new PubKeyWriter instance.
    68  func NewWriter(w io.Writer) *PubKeyWriter {
    69  	return &PubKeyWriter{
    70  		w: w,
    71  	}
    72  }
    73  
    74  // Write writes the public keyset to the underlying w.Writer.
    75  func (p *PubKeyWriter) Write(ks *tinkpb.Keyset) error {
    76  	return p.write(ks)
    77  }
    78  
    79  // WriteEncrypted writes the encrypted keyset to the underlying w.Writer.
    80  func (p *PubKeyWriter) WriteEncrypted(_ *tinkpb.EncryptedKeyset) error {
    81  	return fmt.Errorf("write encrypted function not supported")
    82  }
    83  
    84  func (p *PubKeyWriter) write(msg *tinkpb.Keyset) error {
    85  	ks := msg.Key
    86  	primaryKID := msg.PrimaryKeyId
    87  	created := false
    88  
    89  	var err error
    90  
    91  	for _, key := range ks {
    92  		if key.KeyId == primaryKID && key.Status == tinkpb.KeyStatusType_ENABLED {
    93  			created, err = p.writePubKey(key)
    94  			if err != nil {
    95  				return err
    96  			}
    97  
    98  			break
    99  		}
   100  	}
   101  
   102  	if !created {
   103  		return fmt.Errorf("key not written")
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (p *PubKeyWriter) writePubKey(key *tinkpb.Keyset_Key) (bool, error) {
   110  	pubKey, kt, err := protoToCompositeKey(key.KeyData)
   111  	if err != nil {
   112  		return false, err
   113  	}
   114  
   115  	mPubKey, err := json.Marshal(pubKey)
   116  	if err != nil {
   117  		return false, err
   118  	}
   119  
   120  	n, err := p.w.Write(mPubKey)
   121  	if err != nil {
   122  		return false, err
   123  	}
   124  
   125  	p.KeyType = kt
   126  
   127  	return n > 0, nil
   128  }
   129  
   130  func protoToCompositeKey(keyData *tinkpb.KeyData) (*cryptoapi.PublicKey, kms.KeyType, error) {
   131  	var (
   132  		cKey compositeKeyGetter
   133  		err  error
   134  	)
   135  
   136  	switch keyData.TypeUrl {
   137  	case nistPECDHKWPublicKeyTypeURL, x25519ECDHKWPublicKeyTypeURL:
   138  		cKey, err = newECDHKey(keyData.Value)
   139  		if err != nil {
   140  			return nil, "", err
   141  		}
   142  	default:
   143  		return nil, "", fmt.Errorf("can't export key with keyURL:%s", keyData.TypeUrl)
   144  	}
   145  
   146  	return buildKey(cKey)
   147  }
   148  
   149  func buildKey(c compositeKeyGetter) (*cryptoapi.PublicKey, kms.KeyType, error) {
   150  	curveName := c.curveName()
   151  	keyTypeName := c.keyType()
   152  
   153  	return buildCompositeKey(c.kid(), keyTypeName, curveName, c.x(), c.y())
   154  }
   155  
   156  func buildCompositeKey(kid, keyType, curve string, x, y []byte) (*cryptoapi.PublicKey, kms.KeyType, error) {
   157  	var kt kms.KeyType
   158  
   159  	// validate keyType and curve
   160  	switch keyType {
   161  	case ecdhpb.KeyType_EC.String():
   162  		// validate NIST P curves
   163  		_, err := hybrid.GetCurve(curve)
   164  		if err != nil {
   165  			return nil, "", fmt.Errorf("undefined EC curve: %w", err)
   166  		}
   167  
   168  		kt = ecdhKeyTypes[curve]
   169  	case ecdhpb.KeyType_OKP.String():
   170  		if curve != commonpb.EllipticCurveType_CURVE25519.String() {
   171  			return nil, "", fmt.Errorf("invalid OKP curve: %s", curve)
   172  		}
   173  
   174  		// use JWK curve name when exporting the key.
   175  		curve = "X25519"
   176  		kt = kms.X25519ECDHKWType
   177  	default:
   178  		return nil, "", fmt.Errorf("invalid keyType: %s", keyType)
   179  	}
   180  
   181  	return &cryptoapi.PublicKey{
   182  		KID:   kid,
   183  		Type:  keyType,
   184  		Curve: curve,
   185  		X:     x,
   186  		Y:     y,
   187  	}, kt, nil
   188  }
   189  
   190  type compositeKeyGetter interface {
   191  	kid() string
   192  	curveName() string
   193  	keyType() string
   194  	x() []byte
   195  	y() []byte
   196  }
   197  
   198  type ecdhKey struct {
   199  	protoKey *ecdhpb.EcdhAeadPublicKey
   200  }
   201  
   202  func newECDHKey(mKey []byte) (compositeKeyGetter, error) {
   203  	pubKeyProto := new(ecdhpb.EcdhAeadPublicKey)
   204  
   205  	err := proto.Unmarshal(mKey, pubKeyProto)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	return &ecdhKey{protoKey: pubKeyProto}, nil
   211  }
   212  
   213  func (e *ecdhKey) kid() string {
   214  	return e.protoKey.KID
   215  }
   216  
   217  func (e *ecdhKey) curveName() string {
   218  	return e.protoKey.Params.KwParams.CurveType.String()
   219  }
   220  
   221  func (e *ecdhKey) keyType() string {
   222  	return e.protoKey.Params.KwParams.KeyType.String()
   223  }
   224  
   225  func (e *ecdhKey) x() []byte {
   226  	return e.protoKey.X
   227  }
   228  
   229  func (e *ecdhKey) y() []byte {
   230  	return e.protoKey.Y
   231  }
   232  
   233  // ExtractPrimaryPublicKey is a utility function that will extract the main public key from *keyset.Handle kh.
   234  func ExtractPrimaryPublicKey(kh *keyset.Handle) (*cryptoapi.PublicKey, error) {
   235  	keyBytes, err := writePubKeyFromKeyHandle(kh)
   236  	if err != nil {
   237  		return nil, fmt.Errorf("extractPrimaryPublicKey: failed to get public key content: %w", err)
   238  	}
   239  
   240  	ecPubKey := new(cryptoapi.PublicKey)
   241  
   242  	err = json.Unmarshal(keyBytes, ecPubKey)
   243  	if err != nil {
   244  		return nil, fmt.Errorf("extractPrimaryPublicKey: unmarshal key failed: %w", err)
   245  	}
   246  
   247  	return ecPubKey, nil
   248  }
   249  
   250  func writePubKeyFromKeyHandle(handle *keyset.Handle) ([]byte, error) {
   251  	pubKH, err := handle.Public()
   252  	if err != nil {
   253  		if strings.HasSuffix(err.Error(), "keyset contains a non-private key") {
   254  			pubKH = handle
   255  		} else {
   256  			return nil, err
   257  		}
   258  	}
   259  
   260  	buf := new(bytes.Buffer)
   261  	pubKeyWriter := NewWriter(buf)
   262  
   263  	err = pubKH.WriteWithNoSecrets(pubKeyWriter)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	return buf.Bytes(), nil
   269  }
   270  
   271  // PublicKeyToKeysetHandle converts pubKey into a *keyset.Handle where pubKey could be either a sender or a
   272  // recipient key. The resulting handle cannot be directly used for primitive execution as the cek is not set. This
   273  // function serves as a helper to get a senderKH to be used as an option for ECDH execution (for ECDH-1PU/authcrypt).
   274  // The keyset handle will be set with either AES256-GCM, AES128CBC+SHA256, AES192CBC+SHA384, AES256CBC+SHA384 or
   275  // AES256CBC+SHA512 AEAD key template for content encryption. With:
   276  // - pubKey the public key to convert.
   277  // - aeadAlg the content encryption algorithm to use along the ECDH primitive.
   278  func PublicKeyToKeysetHandle(pubKey *cryptoapi.PublicKey, aeadAlg ecdh.AEADAlg) (*keyset.Handle, error) {
   279  	// validate curve
   280  	cp, err := getCurveProto(pubKey.Curve)
   281  	if err != nil {
   282  		return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to convert curve string to proto: %w", err)
   283  	}
   284  
   285  	kt, err := getKeyType(pubKey.Type)
   286  	if err != nil {
   287  		return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to convert key type to proto: %w", err)
   288  	}
   289  
   290  	encT, keyURL, err := keyTemplateAndURL(cp, aeadAlg, true)
   291  	if err != nil {
   292  		return nil, fmt.Errorf("publicKeyToKeysetHandle: %w", err)
   293  	}
   294  
   295  	protoKey := &ecdhpb.EcdhAeadPublicKey{
   296  		Version: 0,
   297  		Params: &ecdhpb.EcdhAeadParams{
   298  			KwParams: &ecdhpb.EcdhKwParams{
   299  				CurveType: cp,
   300  				KeyType:   kt,
   301  			},
   302  			EncParams: &ecdhpb.EcdhAeadEncParams{
   303  				AeadEnc: encT,
   304  			},
   305  			EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED,
   306  		},
   307  		KID: pubKey.KID,
   308  		X:   pubKey.X,
   309  		Y:   pubKey.Y,
   310  	}
   311  
   312  	marshalledKey, err := proto.Marshal(protoKey)
   313  	if err != nil {
   314  		return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to marshal proto: %w", err)
   315  	}
   316  
   317  	ks := newKeySet(keyURL, marshalledKey, tinkpb.KeyData_ASYMMETRIC_PUBLIC)
   318  
   319  	memReader := &keyset.MemReaderWriter{Keyset: ks}
   320  
   321  	parsedHandle, err := insecurecleartextkeyset.Read(memReader)
   322  	if err != nil {
   323  		return nil, fmt.Errorf("publicKeyToKeysetHandle: failed to create key handle: %w", err)
   324  	}
   325  
   326  	return parsedHandle, nil
   327  }
   328  
   329  // PrivateKeyToKeysetHandle converts privKey into a *keyset.Handle where privKey could be either a sender or a
   330  // recipient key. The resulting handle cannot be directly used for primitive execution as the cek is not set. This
   331  // function serves as a helper to get a senderKH to be used as an option for ECDH execution (for ECDH-1PU/authcrypt).
   332  // The keyset handle will be set with either AES256-GCM, AES128CBC+SHA256, AES192CBC+SHA384, AES256CBC+SHA384 or
   333  // AES256CBC+SHA512 AEAD key template for content encryption. With:
   334  // - privKey the private key to convert.
   335  // - aeadAlg the content encryption algorithm to use along the ECDH primitive.
   336  func PrivateKeyToKeysetHandle(privKey *cryptoapi.PrivateKey, aeadAlg ecdh.AEADAlg) (*keyset.Handle, error) {
   337  	// validate curve
   338  	cp, err := getCurveProto(privKey.PublicKey.Curve)
   339  	if err != nil {
   340  		return nil, fmt.Errorf("privateKeyToKeysetHandle: failed to convert curve string to proto: %w", err)
   341  	}
   342  
   343  	kt, err := getKeyType(privKey.PublicKey.Type)
   344  	if err != nil {
   345  		return nil, fmt.Errorf("privateKeyToKeysetHandle: failed to convert key type to proto: %w", err)
   346  	}
   347  
   348  	encT, keyURL, err := keyTemplateAndURL(cp, aeadAlg, false)
   349  	if err != nil {
   350  		return nil, fmt.Errorf("privateKeyToKeysetHandle: %w", err)
   351  	}
   352  
   353  	protoKey := &ecdhpb.EcdhAeadPrivateKey{
   354  		Version: 0,
   355  		PublicKey: &ecdhpb.EcdhAeadPublicKey{
   356  			Version: 0,
   357  			Params: &ecdhpb.EcdhAeadParams{
   358  				KwParams: &ecdhpb.EcdhKwParams{
   359  					CurveType: cp,
   360  					KeyType:   kt,
   361  				},
   362  				EncParams: &ecdhpb.EcdhAeadEncParams{
   363  					AeadEnc: encT,
   364  				},
   365  				EcPointFormat: commonpb.EcPointFormat_UNCOMPRESSED,
   366  			},
   367  			KID: privKey.PublicKey.KID,
   368  			X:   privKey.PublicKey.X,
   369  			Y:   privKey.PublicKey.Y,
   370  		},
   371  		KeyValue: privKey.D,
   372  	}
   373  
   374  	marshalledKey, err := proto.Marshal(protoKey)
   375  	if err != nil {
   376  		return nil, fmt.Errorf("privateKeyToKeysetHandle: failed to marshal proto: %w", err)
   377  	}
   378  
   379  	ks := newKeySet(keyURL, marshalledKey, tinkpb.KeyData_ASYMMETRIC_PRIVATE)
   380  
   381  	memReader := &keyset.MemReaderWriter{Keyset: ks}
   382  
   383  	parsedHandle, err := insecurecleartextkeyset.Read(memReader)
   384  	if err != nil {
   385  		return nil, fmt.Errorf("privateKeyToKeysetHandle: failed to create key handle: %w", err)
   386  	}
   387  
   388  	return parsedHandle, nil
   389  }
   390  
   391  var keyTemplateToPublicKeyURL = map[commonpb.EllipticCurveType]string{ //nolint:gochecknoglobals
   392  	commonpb.EllipticCurveType_NIST_P256:  nistPECDHKWPublicKeyTypeURL,
   393  	commonpb.EllipticCurveType_NIST_P384:  nistPECDHKWPublicKeyTypeURL,
   394  	commonpb.EllipticCurveType_NIST_P521:  nistPECDHKWPublicKeyTypeURL,
   395  	commonpb.EllipticCurveType_CURVE25519: x25519ECDHKWPublicKeyTypeURL,
   396  }
   397  
   398  var keyTemplateToPrivateKeyURL = map[commonpb.EllipticCurveType]string{ //nolint:gochecknoglobals
   399  	commonpb.EllipticCurveType_NIST_P256:  nistPECDHKWPrivateKeyTypeURL,
   400  	commonpb.EllipticCurveType_NIST_P384:  nistPECDHKWPrivateKeyTypeURL,
   401  	commonpb.EllipticCurveType_NIST_P521:  nistPECDHKWPrivateKeyTypeURL,
   402  	commonpb.EllipticCurveType_CURVE25519: x25519ECDHKWPrivateKeyTypeURL,
   403  }
   404  
   405  func keyTemplateAndURL(cp commonpb.EllipticCurveType, aeadAlg ecdh.AEADAlg,
   406  	isPublic bool) (*tinkpb.KeyTemplate, string, error) {
   407  	// set ecdh kw public keyTypeURL.
   408  	var (
   409  		encT   *tinkpb.KeyTemplate
   410  		keyURL string
   411  	)
   412  
   413  	if isPublic {
   414  		keyURL = keyTemplateToPublicKeyURL[cp]
   415  	} else {
   416  		keyURL = keyTemplateToPrivateKeyURL[cp]
   417  	}
   418  
   419  	if keyURL == "" {
   420  		return nil, "", fmt.Errorf("invalid key curve: '%v'", cp)
   421  	}
   422  
   423  	// set aeadAlg encryption primitive template.
   424  	switch aeadAlg {
   425  	case ecdh.AES256GCM:
   426  		encT = tinkaead.AES256GCMKeyTemplate()
   427  	case ecdh.XC20P:
   428  		encT = tinkaead.XChaCha20Poly1305KeyTemplate()
   429  	case ecdh.AES128CBCHMACSHA256:
   430  		encT = aead.AES128CBCHMACSHA256KeyTemplate()
   431  	case ecdh.AES192CBCHMACSHA384:
   432  		encT = aead.AES192CBCHMACSHA384KeyTemplate()
   433  	case ecdh.AES256CBCHMACSHA384:
   434  		encT = aead.AES256CBCHMACSHA384KeyTemplate()
   435  	case ecdh.AES256CBCHMACSHA512:
   436  		encT = aead.AES256CBCHMACSHA512KeyTemplate()
   437  	default:
   438  		return nil, "", fmt.Errorf("invalid encryption algorithm: '%v'", ecdh.EncryptionAlgLabel[aeadAlg])
   439  	}
   440  
   441  	return encT, keyURL, nil
   442  }
   443  
   444  func getCurveProto(c string) (commonpb.EllipticCurveType, error) {
   445  	switch c {
   446  	case "secp256r1", "NIST_P256", "P-256", "EllipticCurveType_NIST_P256":
   447  		return commonpb.EllipticCurveType_NIST_P256, nil
   448  	case "secp384r1", "NIST_P384", "P-384", "EllipticCurveType_NIST_P384":
   449  		return commonpb.EllipticCurveType_NIST_P384, nil
   450  	case "secp521r1", "NIST_P521", "P-521", "EllipticCurveType_NIST_P521":
   451  		return commonpb.EllipticCurveType_NIST_P521, nil
   452  	case commonpb.EllipticCurveType_CURVE25519.String(), "X25519":
   453  		return commonpb.EllipticCurveType_CURVE25519, nil
   454  	default:
   455  		return commonpb.EllipticCurveType_UNKNOWN_CURVE, errors.New("unsupported curve")
   456  	}
   457  }
   458  
   459  func getKeyType(k string) (ecdhpb.KeyType, error) {
   460  	switch k {
   461  	case ecdhpb.KeyType_EC.String():
   462  		return ecdhpb.KeyType_EC, nil
   463  	case ecdhpb.KeyType_OKP.String():
   464  		return ecdhpb.KeyType_OKP, nil
   465  	default:
   466  		return ecdhpb.KeyType_UNKNOWN_KEY_TYPE, errors.New("unsupported key type")
   467  	}
   468  }
   469  
   470  func newKeySet(tURL string, marshalledKey []byte, keyMaterialType tinkpb.KeyData_KeyMaterialType) *tinkpb.Keyset {
   471  	keyData := &tinkpb.KeyData{
   472  		TypeUrl:         tURL,
   473  		Value:           marshalledKey,
   474  		KeyMaterialType: keyMaterialType,
   475  	}
   476  
   477  	return &tinkpb.Keyset{
   478  		Key: []*tinkpb.Keyset_Key{
   479  			{
   480  				KeyData: keyData,
   481  				Status:  tinkpb.KeyStatusType_ENABLED,
   482  				KeyId:   1,
   483  				// since we're building the key from raw key bytes, then must use raw key prefix type
   484  				OutputPrefixType: tinkpb.OutputPrefixType_RAW,
   485  			},
   486  		},
   487  		PrimaryKeyId: 1,
   488  	}
   489  }