fakekms/asymmetric_rpcs.go (172 lines of code) (raw):

// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fakekms import ( "context" "crypto" "crypto/rand" "crypto/x509" "encoding/pem" "hash/crc32" "cloud.google.com/go/kms/apiv1/kmspb" "google.golang.org/protobuf/types/known/wrapperspb" ) var crc32cTable = crc32.MakeTable(crc32.Castagnoli) func crc32c(data []byte) *wrapperspb.Int64Value { return wrapperspb.Int64(int64(crc32.Checksum(data, crc32cTable))) } // GetPublicKey fakes a Cloud KMS API function. func (f *fakeKMS) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest) (*kmspb.PublicKey, error) { if err := allowlist("name").check(req); err != nil { return nil, err } name, err := parseCryptoKeyVersionName(req.Name) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } if ckv.pb.State != kmspb.CryptoKeyVersion_ENABLED { return nil, errFailedPrecondition("key version %s is not enabled", name) } s, ok := ckv.keyMaterial.(crypto.Signer) if !ok { return nil, errFailedPrecondition("keys with algorithm %s do not contain a public key", nameForValue(kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm_name, int32(ckv.pb.Algorithm))) } derPub, err := x509.MarshalPKIXPublicKey(s.Public()) if err != nil { return nil, err } pemPub := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: derPub, }) return &kmspb.PublicKey{ Name: req.Name, Algorithm: ckv.pb.Algorithm, Pem: string(pemPub), PemCrc32C: crc32c(pemPub), ProtectionLevel: ckv.pb.ProtectionLevel, }, nil } // AsymmetricDecrypt fakes a Cloud KMS API function. func (f *fakeKMS) AsymmetricDecrypt(ctx context.Context, req *kmspb.AsymmetricDecryptRequest) (*kmspb.AsymmetricDecryptResponse, error) { if err := allowlist("name", "ciphertext", "ciphertext_crc32c").check(req); err != nil { return nil, err } name, err := parseCryptoKeyVersionName(req.Name) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } if ckv.pb.State != kmspb.CryptoKeyVersion_ENABLED { return nil, errFailedPrecondition("key version %s is not enabled", name) } def, _ := algorithmDef(ckv.pb.Algorithm) if def.Purpose != kmspb.CryptoKey_ASYMMETRIC_DECRYPT { return nil, errFailedPrecondition("keys with algorithm %s may not be used for asymmetric decryption", nameForValue(kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm_name, int32(ckv.pb.Algorithm))) } if req.Ciphertext == nil { return nil, errInvalidArgument("ciphertext is empty") } if len(req.Ciphertext) > maxCiphertextSize { return nil, errInvalidArgument("len(ciphertext)=%d, want len(ciphertext)<=%d", len(req.Ciphertext), maxCiphertextSize) } ciphertextChecksum := crc32c(req.Ciphertext) if req.CiphertextCrc32C != nil && ciphertextChecksum.Value != req.CiphertextCrc32C.Value { return nil, errInvalidArgument("invalid ciphertext checksum") } pt, err := ckv.keyMaterial.(crypto.Decrypter).Decrypt(rand.Reader, req.Ciphertext, def.Opts) if err != nil { return nil, errInvalidArgument("decryption failed: %v", err) } return &kmspb.AsymmetricDecryptResponse{ Plaintext: pt, PlaintextCrc32C: crc32c(pt), VerifiedCiphertextCrc32C: req.CiphertextCrc32C != nil, ProtectionLevel: ckv.pb.ProtectionLevel, }, nil } // AsymmetricSign fakes a Cloud KMS API function. func (f *fakeKMS) AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest) (*kmspb.AsymmetricSignResponse, error) { if err := allowlist("name", "data", "data_crc32c", "digest.sha256", "digest.sha384", "digest.sha512", "digest_crc32c").check(req); err != nil { return nil, err } name, err := parseCryptoKeyVersionName(req.Name) if err != nil { return nil, err } ckv, err := f.cryptoKeyVersion(name) if err != nil { return nil, err } if ckv.pb.State != kmspb.CryptoKeyVersion_ENABLED { return nil, errFailedPrecondition("key version %s is not enabled", name) } def, _ := algorithmDef(ckv.pb.Algorithm) if def.Purpose != kmspb.CryptoKey_ASYMMETRIC_SIGN { return nil, errFailedPrecondition("keys with algorithm %s may not be used for signing", nameForValue(kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm_name, int32(ckv.pb.Algorithm))) } if req.DataCrc32C != nil && req.DigestCrc32C != nil { return nil, errInvalidArgument("only one of digest_crc32c or data_crc32c must be set") } opts := def.Opts.(crypto.SignerOpts) var data []byte switch { case req.Digest != nil && req.Data != nil: return nil, errInvalidArgument("only one of digest or data must be set") case req.Digest == nil && req.Data == nil: return nil, errInvalidArgument("at least one of digest or data must be set") case opts.HashFunc() == crypto.Hash(0): // algorithm doesn't support prehashed data if req.Data == nil { return nil, errInvalidArgument("data is empty") } dataChecksum := crc32c(req.Data) if req.DataCrc32C != nil && dataChecksum.Value != req.DataCrc32C.Value { return nil, errInvalidArgument("invalid data checksum") } data = req.Data case req.Data != nil: if req.Digest != nil { return nil, errInvalidArgument("digest should be empty") } // Validate checksum before computing the digest dataChecksum := crc32c(req.Data) if req.DataCrc32C != nil && dataChecksum.Value != req.DataCrc32C.Value { return nil, errInvalidArgument("invalid data checksum") } data = req.Data h := opts.HashFunc().New() h.Write(data) data = h.Sum(nil) case opts.HashFunc() == crypto.SHA256: data = req.Digest.GetSha256() case opts.HashFunc() == crypto.SHA384: data = req.Digest.GetSha384() case opts.HashFunc() == crypto.SHA512: data = req.Digest.GetSha512() default: return nil, errInternal("unsupported hash: %d", opts.HashFunc()) } if opts.HashFunc() != crypto.Hash(0) && len(data) != opts.HashFunc().Size() { return nil, errInvalidArgument("len(digest)=%d, want %d", len(data), opts.HashFunc().Size()) } dataChecksum := crc32c(data) if req.DigestCrc32C != nil && dataChecksum.Value != req.DigestCrc32C.Value { return nil, errInvalidArgument("invalid digest checksum") } sig, err := ckv.keyMaterial.(crypto.Signer).Sign(rand.Reader, data, opts) if err != nil { return nil, errInternal("signing failed: %v", err) } return &kmspb.AsymmetricSignResponse{ Name: req.Name, Signature: sig, SignatureCrc32C: crc32c(sig), VerifiedDataCrc32C: req.DataCrc32C != nil, VerifiedDigestCrc32C: req.DigestCrc32C != nil, ProtectionLevel: ckv.pb.ProtectionLevel, }, nil }