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 }