// Copyright 2023 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. // //////////////////////////////////////////////////////////////////////////////// #include "tink/aead/aes_gcm_proto_serialization.h" #include #include "absl/status/status.h" #include "absl/types/optional.h" #include "tink/aead/aes_gcm_key.h" #include "tink/aead/aes_gcm_parameters.h" #include "tink/internal/key_parser.h" #include "tink/internal/key_serializer.h" #include "tink/internal/mutable_serialization_registry.h" #include "tink/internal/parameters_parser.h" #include "tink/internal/parameters_serializer.h" #include "tink/internal/proto_key_serialization.h" #include "tink/internal/proto_parameters_serialization.h" #include "tink/partial_key_access.h" #include "tink/restricted_data.h" #include "tink/secret_key_access_token.h" #include "tink/util/status.h" #include "tink/util/statusor.h" #include "proto/aes_gcm.pb.h" #include "proto/tink.pb.h" namespace crypto { namespace tink { namespace { using ::google::crypto::tink::AesGcmKeyFormat; using ::google::crypto::tink::OutputPrefixType; using AesGcmProtoParametersParserImpl = internal::ParametersParserImpl; using AesGcmProtoParametersSerializerImpl = internal::ParametersSerializerImpl; using AesGcmProtoKeyParserImpl = internal::KeyParserImpl; using AesGcmProtoKeySerializerImpl = internal::KeySerializerImpl; const absl::string_view kTypeUrl = "type.googleapis.com/google.crypto.tink.AesGcmKey"; util::StatusOr ToVariant( OutputPrefixType output_prefix_type) { switch (output_prefix_type) { case OutputPrefixType::LEGACY: ABSL_FALLTHROUGH_INTENDED; // Parse LEGACY output prefix as CRUNCHY. case OutputPrefixType::CRUNCHY: return AesGcmParameters::Variant::kCrunchy; case OutputPrefixType::RAW: return AesGcmParameters::Variant::kNoPrefix; case OutputPrefixType::TINK: return AesGcmParameters::Variant::kTink; default: return util::Status(absl::StatusCode::kInvalidArgument, "Could not determine AesGcmParameters::Variant"); } } util::StatusOr ToOutputPrefixType( AesGcmParameters::Variant variant) { switch (variant) { case AesGcmParameters::Variant::kCrunchy: return OutputPrefixType::CRUNCHY; case AesGcmParameters::Variant::kNoPrefix: return OutputPrefixType::RAW; case AesGcmParameters::Variant::kTink: return OutputPrefixType::TINK; default: return util::Status(absl::StatusCode::kInvalidArgument, "Could not determine output prefix type"); } } // Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte // tags. util::Status ValidateParamsForProto(const AesGcmParameters& params) { if (params.IvSizeInBytes() != 12) { return util::Status( absl::StatusCode::kInvalidArgument, "Tink currently restricts AES-GCM IV size to 12 bytes."); } if (params.TagSizeInBytes() != 16) { return util::Status( absl::StatusCode::kInvalidArgument, "Tink currently restricts AES-GCM tag size to 16 bytes."); } return util::OkStatus(); } util::StatusOr ParseParameters( const internal::ProtoParametersSerialization& serialization) { if (serialization.GetKeyTemplate().type_url() != kTypeUrl) { return util::Status(absl::StatusCode::kInvalidArgument, "Wrong type URL when parsing AesGcmParameters."); } AesGcmKeyFormat proto_key_format; if (!proto_key_format.ParseFromString( serialization.GetKeyTemplate().value())) { return util::Status(absl::StatusCode::kInvalidArgument, "Failed to parse AesGcmKeyFormat proto"); } if (proto_key_format.version() != 0) { return util::Status(absl::StatusCode::kInvalidArgument, "Only version 0 keys are accepted."); } util::StatusOr variant = ToVariant(serialization.GetKeyTemplate().output_prefix_type()); if (!variant.ok()) return variant.status(); // Legacy Tink AES-GCM key proto format assumes 12-byte random IVs and 16-byte // tags. return AesGcmParameters::Builder() .SetVariant(*variant) .SetKeySizeInBytes(proto_key_format.key_size()) .SetIvSizeInBytes(12) .SetTagSizeInBytes(16) .Build(); } util::StatusOr SerializeParameters( const AesGcmParameters& parameters) { util::Status valid_params = ValidateParamsForProto(parameters); if (!valid_params.ok()) return valid_params; util::StatusOr output_prefix_type = ToOutputPrefixType(parameters.GetVariant()); if (!output_prefix_type.ok()) return output_prefix_type.status(); AesGcmKeyFormat proto_key_format; proto_key_format.set_key_size(parameters.KeySizeInBytes()); return internal::ProtoParametersSerialization::Create( kTypeUrl, *output_prefix_type, proto_key_format.SerializeAsString()); } util::StatusOr ParseKey( const internal::ProtoKeySerialization& serialization, absl::optional token) { if (serialization.TypeUrl() != kTypeUrl) { return util::Status(absl::StatusCode::kInvalidArgument, "Wrong type URL when parsing AesGcmKey."); } if (!token.has_value()) { return util::Status(absl::StatusCode::kInvalidArgument, "SecretKeyAccess is required"); } google::crypto::tink::AesGcmKey proto_key; RestrictedData restricted_data = serialization.SerializedKeyProto(); // OSS proto library complains if input is not converted to a string. if (!proto_key.ParseFromString( std::string(restricted_data.GetSecret(*token)))) { return util::Status(absl::StatusCode::kInvalidArgument, "Failed to parse AesGcmKey proto"); } if (proto_key.version() != 0) { return util::Status(absl::StatusCode::kInvalidArgument, "Only version 0 keys are accepted."); } util::StatusOr variant = ToVariant(serialization.GetOutputPrefixType()); if (!variant.ok()) return variant.status(); // Legacy AES-GCM key proto format assumes 12-byte random IVs and 16-byte // tags. util::StatusOr parameters = AesGcmParameters::Builder() .SetVariant(*variant) .SetKeySizeInBytes(proto_key.key_value().length()) .SetIvSizeInBytes(12) .SetTagSizeInBytes(16) .Build(); if (!parameters.ok()) return parameters.status(); return AesGcmKey::Create( *parameters, RestrictedData(proto_key.key_value(), *token), serialization.IdRequirement(), GetPartialKeyAccess()); } util::StatusOr SerializeKey( const AesGcmKey& key, absl::optional token) { util::Status valid_params = ValidateParamsForProto(key.GetParameters()); if (!valid_params.ok()) return valid_params; util::StatusOr restricted_input = key.GetKeyBytes(GetPartialKeyAccess()); if (!restricted_input.ok()) return restricted_input.status(); if (!token.has_value()) { return util::Status(absl::StatusCode::kInvalidArgument, "SecretKeyAccess is required"); } google::crypto::tink::AesGcmKey proto_key; proto_key.set_version(0); // OSS proto library complains if input is not converted to a string. proto_key.set_key_value(std::string(restricted_input->GetSecret(*token))); util::StatusOr output_prefix_type = ToOutputPrefixType(key.GetParameters().GetVariant()); if (!output_prefix_type.ok()) return output_prefix_type.status(); RestrictedData restricted_output = RestrictedData(proto_key.SerializeAsString(), *token); return internal::ProtoKeySerialization::Create( kTypeUrl, restricted_output, google::crypto::tink::KeyData::SYMMETRIC, *output_prefix_type, key.GetIdRequirement()); } AesGcmProtoParametersParserImpl* AesGcmProtoParametersParser() { static auto* parser = new AesGcmProtoParametersParserImpl(kTypeUrl, ParseParameters); return parser; } AesGcmProtoParametersSerializerImpl* AesGcmProtoParametersSerializer() { static auto* serializer = new AesGcmProtoParametersSerializerImpl(kTypeUrl, SerializeParameters); return serializer; } AesGcmProtoKeyParserImpl* AesGcmProtoKeyParser() { static auto* parser = new AesGcmProtoKeyParserImpl(kTypeUrl, ParseKey); return parser; } AesGcmProtoKeySerializerImpl* AesGcmProtoKeySerializer() { static auto* serializer = new AesGcmProtoKeySerializerImpl(SerializeKey); return serializer; } } // namespace util::Status RegisterAesGcmProtoSerialization() { util::Status status = internal::MutableSerializationRegistry::GlobalInstance() .RegisterParametersParser(AesGcmProtoParametersParser()); if (!status.ok()) return status; status = internal::MutableSerializationRegistry::GlobalInstance() .RegisterParametersSerializer(AesGcmProtoParametersSerializer()); if (!status.ok()) return status; status = internal::MutableSerializationRegistry::GlobalInstance() .RegisterKeyParser(AesGcmProtoKeyParser()); if (!status.ok()) return status; return internal::MutableSerializationRegistry::GlobalInstance() .RegisterKeySerializer(AesGcmProtoKeySerializer()); } } // namespace tink } // namespace crypto