All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.crypto.tink.hybrid.internal.EciesProtoSerialization Maven / Gradle / Ivy

// 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.
//
////////////////////////////////////////////////////////////////////////////////

package com.google.crypto.tink.hybrid.internal;

import static com.google.crypto.tink.internal.Util.toBytesFromPrintableAscii;

import com.google.crypto.tink.AccessesPartialKey;
import com.google.crypto.tink.SecretKeyAccess;
import com.google.crypto.tink.TinkProtoParametersFormat;
import com.google.crypto.tink.hybrid.EciesParameters;
import com.google.crypto.tink.hybrid.EciesPrivateKey;
import com.google.crypto.tink.hybrid.EciesPublicKey;
import com.google.crypto.tink.internal.BigIntegerEncoding;
import com.google.crypto.tink.internal.EnumTypeProtoConverter;
import com.google.crypto.tink.internal.KeyParser;
import com.google.crypto.tink.internal.KeySerializer;
import com.google.crypto.tink.internal.MutableSerializationRegistry;
import com.google.crypto.tink.internal.ParametersParser;
import com.google.crypto.tink.internal.ParametersSerializer;
import com.google.crypto.tink.internal.ProtoKeySerialization;
import com.google.crypto.tink.internal.ProtoParametersSerialization;
import com.google.crypto.tink.proto.EcPointFormat;
import com.google.crypto.tink.proto.EciesAeadHkdfKeyFormat;
import com.google.crypto.tink.proto.EciesAeadHkdfParams;
import com.google.crypto.tink.proto.EllipticCurveType;
import com.google.crypto.tink.proto.HashType;
import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
import com.google.crypto.tink.proto.KeyTemplate;
import com.google.crypto.tink.proto.OutputPrefixType;
import com.google.crypto.tink.util.Bytes;
import com.google.crypto.tink.util.SecretBigInteger;
import com.google.crypto.tink.util.SecretBytes;
import com.google.protobuf.ByteString;
import com.google.protobuf.ExtensionRegistryLite;
import com.google.protobuf.InvalidProtocolBufferException;
import java.security.GeneralSecurityException;
import java.security.spec.ECPoint;
import javax.annotation.Nullable;

/** Methods to serialize and parse {@link EciesParameters} objects. */
@AccessesPartialKey
@SuppressWarnings("UnnecessarilyFullyQualified") // Fully specifying proto types is more readable
public final class EciesProtoSerialization {
  private static final String PRIVATE_TYPE_URL =
      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
  private static final Bytes PRIVATE_TYPE_URL_BYTES = toBytesFromPrintableAscii(PRIVATE_TYPE_URL);

  private static final String PUBLIC_TYPE_URL =
      "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
  private static final Bytes PUBLIC_TYPE_URL_BYTES = toBytesFromPrintableAscii(PUBLIC_TYPE_URL);

  private static final ParametersSerializer
      PARAMETERS_SERIALIZER =
          ParametersSerializer.create(
              EciesProtoSerialization::serializeParameters,
              EciesParameters.class,
              ProtoParametersSerialization.class);

  private static final ParametersParser PARAMETERS_PARSER =
      ParametersParser.create(
          EciesProtoSerialization::parseParameters,
          PRIVATE_TYPE_URL_BYTES,
          ProtoParametersSerialization.class);

  private static final KeySerializer PUBLIC_KEY_SERIALIZER =
      KeySerializer.create(
          EciesProtoSerialization::serializePublicKey,
          EciesPublicKey.class,
          ProtoKeySerialization.class);

  private static final KeyParser PUBLIC_KEY_PARSER =
      KeyParser.create(
          EciesProtoSerialization::parsePublicKey,
          PUBLIC_TYPE_URL_BYTES,
          ProtoKeySerialization.class);

  private static final KeySerializer
      PRIVATE_KEY_SERIALIZER =
          KeySerializer.create(
              EciesProtoSerialization::serializePrivateKey,
              EciesPrivateKey.class,
              ProtoKeySerialization.class);

  private static final KeyParser PRIVATE_KEY_PARSER =
      KeyParser.create(
          EciesProtoSerialization::parsePrivateKey,
          PRIVATE_TYPE_URL_BYTES,
          ProtoKeySerialization.class);

  private static final EnumTypeProtoConverter
      VARIANT_CONVERTER =
          EnumTypeProtoConverter.builder()
              .add(OutputPrefixType.RAW, EciesParameters.Variant.NO_PREFIX)
              .add(OutputPrefixType.TINK, EciesParameters.Variant.TINK)
              .add(OutputPrefixType.LEGACY, EciesParameters.Variant.CRUNCHY)
              // WARNING: The following mapping MUST be added last to ensure that
              // {@code HpkeParameters.Variant.CRUNCHY} keys are correctly serialized to
              // {@code OutputPrefixType.CRUNCHY} proto keys. Specifically, the most recent entry
              // overrides that toProtoEnum mapping.
              .add(OutputPrefixType.CRUNCHY, EciesParameters.Variant.CRUNCHY)
              .build();

  private static final EnumTypeProtoConverter
      HASH_TYPE_CONVERTER =
          EnumTypeProtoConverter.builder()
              .add(HashType.SHA1, EciesParameters.HashType.SHA1)
              .add(HashType.SHA224, EciesParameters.HashType.SHA224)
              .add(HashType.SHA256, EciesParameters.HashType.SHA256)
              .add(HashType.SHA384, EciesParameters.HashType.SHA384)
              .add(HashType.SHA512, EciesParameters.HashType.SHA512)
              .build();

  private static final EnumTypeProtoConverter
      CURVE_TYPE_CONVERTER =
          EnumTypeProtoConverter.builder()
              .add(EllipticCurveType.NIST_P256, EciesParameters.CurveType.NIST_P256)
              .add(EllipticCurveType.NIST_P384, EciesParameters.CurveType.NIST_P384)
              .add(EllipticCurveType.NIST_P521, EciesParameters.CurveType.NIST_P521)
              .add(EllipticCurveType.CURVE25519, EciesParameters.CurveType.X25519)
              .build();

  private static final EnumTypeProtoConverter
      POINT_FORMAT_CONVERTER =
          EnumTypeProtoConverter.builder()
              .add(EcPointFormat.UNCOMPRESSED, EciesParameters.PointFormat.UNCOMPRESSED)
              .add(EcPointFormat.COMPRESSED, EciesParameters.PointFormat.COMPRESSED)
              .add(
                  EcPointFormat.DO_NOT_USE_CRUNCHY_UNCOMPRESSED,
                  EciesParameters.PointFormat.LEGACY_UNCOMPRESSED)
              .build();

  /**
   * Registers previously defined parser/serializer objects into a global, mutable registry.
   * Registration is public to enable custom configurations.
   */
  public static void register() throws GeneralSecurityException {
    register(MutableSerializationRegistry.globalInstance());
  }

  /** Registers previously defined parser/serializer objects into a given registry. */
  public static void register(MutableSerializationRegistry registry)
      throws GeneralSecurityException {
    registry.registerParametersSerializer(PARAMETERS_SERIALIZER);
    registry.registerParametersParser(PARAMETERS_PARSER);
    registry.registerKeySerializer(PUBLIC_KEY_SERIALIZER);
    registry.registerKeyParser(PUBLIC_KEY_PARSER);
    registry.registerKeySerializer(PRIVATE_KEY_SERIALIZER);
    registry.registerKeyParser(PRIVATE_KEY_PARSER);
  }

  private static com.google.crypto.tink.proto.EciesAeadHkdfParams toProtoParameters(
      EciesParameters parameters) throws GeneralSecurityException {
    com.google.crypto.tink.proto.EciesHkdfKemParams.Builder kemProtoParamsBuilder =
        com.google.crypto.tink.proto.EciesHkdfKemParams.newBuilder()
            .setCurveType(CURVE_TYPE_CONVERTER.toProtoEnum(parameters.getCurveType()))
            .setHkdfHashType(HASH_TYPE_CONVERTER.toProtoEnum(parameters.getHashType()));
    if (parameters.getSalt() != null && parameters.getSalt().size() > 0) {
      kemProtoParamsBuilder.setHkdfSalt(ByteString.copyFrom(parameters.getSalt().toByteArray()));
    }
    com.google.crypto.tink.proto.EciesHkdfKemParams kemProtoParams = kemProtoParamsBuilder.build();

    com.google.crypto.tink.proto.EciesAeadDemParams demProtoParams;
    try {
      KeyTemplate demKeyTemplate =
          KeyTemplate.parseFrom(
              TinkProtoParametersFormat.serialize(parameters.getDemParameters()),
              ExtensionRegistryLite.getEmptyRegistry());
      demProtoParams =
          // Always set OutputPrefixType to TINK when serializing. This is to maintain consistency
          // among the languages.
          com.google.crypto.tink.proto.EciesAeadDemParams.newBuilder()
              .setAeadDem(
                  com.google.crypto.tink.proto.KeyTemplate.newBuilder()
                      .setTypeUrl(demKeyTemplate.getTypeUrl())
                      .setOutputPrefixType(OutputPrefixType.TINK)
                      .setValue(demKeyTemplate.getValue())
                      .build())
              .build();
    } catch (InvalidProtocolBufferException e) {
      throw new GeneralSecurityException("Parsing EciesParameters failed: ", e);
    }

    @Nullable EciesParameters.PointFormat pointFormat = parameters.getNistCurvePointFormat();
    // Null only for X25519 in which case we want compressed.
    if (pointFormat == null) {
      pointFormat = EciesParameters.PointFormat.COMPRESSED;
    }
    return com.google.crypto.tink.proto.EciesAeadHkdfParams.newBuilder()
        .setKemParams(kemProtoParams)
        .setDemParams(demProtoParams)
        .setEcPointFormat(POINT_FORMAT_CONVERTER.toProtoEnum(pointFormat))
        .build();
  }

  private static EciesParameters fromProtoParameters(
      OutputPrefixType outputPrefixType, EciesAeadHkdfParams protoParams)
      throws GeneralSecurityException {
    /* Set OutputPrefixType to RAW when parsing the DEM parameters */
    com.google.crypto.tink.proto.KeyTemplate aeadKeyTemplate =
        com.google.crypto.tink.proto.KeyTemplate.newBuilder()
            .setTypeUrl(protoParams.getDemParams().getAeadDem().getTypeUrl())
            .setOutputPrefixType(OutputPrefixType.RAW)
            .setValue(protoParams.getDemParams().getAeadDem().getValue())
            .build();

    EciesParameters.Builder builder =
        EciesParameters.builder()
            .setVariant(VARIANT_CONVERTER.fromProtoEnum(outputPrefixType))
            .setCurveType(
                CURVE_TYPE_CONVERTER.fromProtoEnum(protoParams.getKemParams().getCurveType()))
            .setHashType(
                HASH_TYPE_CONVERTER.fromProtoEnum(protoParams.getKemParams().getHkdfHashType()))
            .setDemParameters(TinkProtoParametersFormat.parse(aeadKeyTemplate.toByteArray()))
            .setSalt(Bytes.copyFrom(protoParams.getKemParams().getHkdfSalt().toByteArray()));
    if (!protoParams.getKemParams().getCurveType().equals(EllipticCurveType.CURVE25519)) {
      builder.setNistCurvePointFormat(
          POINT_FORMAT_CONVERTER.fromProtoEnum(protoParams.getEcPointFormat()));
    } else {
      if (!protoParams.getEcPointFormat().equals(EcPointFormat.COMPRESSED)) {
        throw new GeneralSecurityException("For CURVE25519 EcPointFormat must be compressed");
      }
    }
    return builder.build();
  }

  private static int getEncodingLength(EciesParameters.CurveType curveType)
      throws GeneralSecurityException {
    // We currently encode with one extra 0 byte at the beginning, to make sure
    // that parsing is correct even if passing of a two's complement encoding is used.
    // See also b/264525021.
    if (EciesParameters.CurveType.NIST_P256.equals(curveType)) {
      return 33;
    }
    if (EciesParameters.CurveType.NIST_P384.equals(curveType)) {
      return 49;
    }
    if (EciesParameters.CurveType.NIST_P521.equals(curveType)) {
      return 67;
    }
    throw new GeneralSecurityException("Unable to serialize CurveType " + curveType);
  }

  private static com.google.crypto.tink.proto.EciesAeadHkdfPublicKey toProtoPublicKey(
      EciesPublicKey key) throws GeneralSecurityException {
    if (key.getParameters().getCurveType().equals(EciesParameters.CurveType.X25519)) {
      return com.google.crypto.tink.proto.EciesAeadHkdfPublicKey.newBuilder()
          .setVersion(0)
          .setParams(toProtoParameters(key.getParameters()))
          .setX(ByteString.copyFrom(key.getX25519CurvePointBytes().toByteArray()))
          .setY(ByteString.EMPTY)
          .build();
    }

    int encLength = getEncodingLength(key.getParameters().getCurveType());
    ECPoint publicPoint = key.getNistCurvePoint();
    if (publicPoint == null) {
      throw new GeneralSecurityException("NistCurvePoint was null for NIST curve");
    }
    return com.google.crypto.tink.proto.EciesAeadHkdfPublicKey.newBuilder()
        .setVersion(0)
        .setParams(toProtoParameters(key.getParameters()))
        .setX(
            ByteString.copyFrom(
                BigIntegerEncoding.toBigEndianBytesOfFixedLength(
                    publicPoint.getAffineX(), encLength)))
        .setY(
            ByteString.copyFrom(
                BigIntegerEncoding.toBigEndianBytesOfFixedLength(
                    publicPoint.getAffineY(), encLength)))
        .build();
  }

  private static com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey toProtoPrivateKey(
      EciesPrivateKey key, @Nullable SecretKeyAccess access) throws GeneralSecurityException {
    com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey.Builder builder =
        com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey.newBuilder()
            .setVersion(0)
            .setPublicKey(toProtoPublicKey(key.getPublicKey()));
    if (key.getParameters().getCurveType().equals(EciesParameters.CurveType.X25519)) {
      builder.setKeyValue(
          ByteString.copyFrom(
              key.getX25519PrivateKeyBytes().toByteArray(SecretKeyAccess.requireAccess(access))));
    } else {
      int encLength = getEncodingLength(key.getParameters().getCurveType());
      builder.setKeyValue(
          ByteString.copyFrom(
              BigIntegerEncoding.toBigEndianBytesOfFixedLength(
                  key.getNistPrivateKeyValue().getBigInteger(SecretKeyAccess.requireAccess(access)),
                  encLength)));
    }
    return builder.build();
  }

  private static ProtoParametersSerialization serializeParameters(EciesParameters parameters)
      throws GeneralSecurityException {
    return ProtoParametersSerialization.create(
        com.google.crypto.tink.proto.KeyTemplate.newBuilder()
            .setTypeUrl(PRIVATE_TYPE_URL)
            .setValue(
                EciesAeadHkdfKeyFormat.newBuilder()
                    .setParams(toProtoParameters(parameters))
                    .build()
                    .toByteString())
            .setOutputPrefixType(VARIANT_CONVERTER.toProtoEnum(parameters.getVariant()))
            .build());
  }

  private static ProtoKeySerialization serializePublicKey(
      EciesPublicKey key, @Nullable SecretKeyAccess access) throws GeneralSecurityException {
    return ProtoKeySerialization.create(
        PUBLIC_TYPE_URL,
        toProtoPublicKey(key).toByteString(),
        KeyMaterialType.ASYMMETRIC_PUBLIC,
        VARIANT_CONVERTER.toProtoEnum(key.getParameters().getVariant()),
        key.getIdRequirementOrNull());
  }

  private static ProtoKeySerialization serializePrivateKey(
      EciesPrivateKey key, @Nullable SecretKeyAccess access) throws GeneralSecurityException {
    return ProtoKeySerialization.create(
        PRIVATE_TYPE_URL,
        toProtoPrivateKey(key, access).toByteString(),
        KeyMaterialType.ASYMMETRIC_PRIVATE,
        VARIANT_CONVERTER.toProtoEnum(key.getParameters().getVariant()),
        key.getIdRequirementOrNull());
  }

  private static EciesParameters parseParameters(ProtoParametersSerialization serialization)
      throws GeneralSecurityException {
    if (!serialization.getKeyTemplate().getTypeUrl().equals(PRIVATE_TYPE_URL)) {
      throw new IllegalArgumentException(
          "Wrong type URL in call to EciesProtoSerialization.parseParameters: "
              + serialization.getKeyTemplate().getTypeUrl());
    }
    EciesAeadHkdfKeyFormat format;
    try {
      format =
          EciesAeadHkdfKeyFormat.parseFrom(
              serialization.getKeyTemplate().getValue(), ExtensionRegistryLite.getEmptyRegistry());
    } catch (InvalidProtocolBufferException e) {
      throw new GeneralSecurityException("Parsing EciesParameters failed: ", e);
    }
    return fromProtoParameters(
        serialization.getKeyTemplate().getOutputPrefixType(), format.getParams());
  }

  @SuppressWarnings("UnusedException")
  private static EciesPublicKey parsePublicKey(
      ProtoKeySerialization serialization, @Nullable SecretKeyAccess access)
      throws GeneralSecurityException {
    if (!serialization.getTypeUrl().equals(PUBLIC_TYPE_URL)) {
      throw new IllegalArgumentException(
          "Wrong type URL in call to EciesProtoSerialization.parsePublicKey: "
              + serialization.getTypeUrl());
    }
    try {
      com.google.crypto.tink.proto.EciesAeadHkdfPublicKey protoKey =
          com.google.crypto.tink.proto.EciesAeadHkdfPublicKey.parseFrom(
              serialization.getValue(), ExtensionRegistryLite.getEmptyRegistry());
      if (protoKey.getVersion() != 0) {
        throw new GeneralSecurityException("Only version 0 keys are accepted");
      }
      EciesParameters parameters =
          fromProtoParameters(serialization.getOutputPrefixType(), protoKey.getParams());
      if (parameters.getCurveType().equals(EciesParameters.CurveType.X25519)) {
        if (!protoKey.getY().isEmpty()) {
          throw new GeneralSecurityException("Y must be empty for X25519 points");
        }
        return EciesPublicKey.createForCurveX25519(
            parameters,
            Bytes.copyFrom(protoKey.getX().toByteArray()),
            serialization.getIdRequirementOrNull());
      }
      ECPoint point =
          new ECPoint(
              BigIntegerEncoding.fromUnsignedBigEndianBytes(protoKey.getX().toByteArray()),
              BigIntegerEncoding.fromUnsignedBigEndianBytes(protoKey.getY().toByteArray()));

      return EciesPublicKey.createForNistCurve(
          parameters, point, serialization.getIdRequirementOrNull());
    } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
      throw new GeneralSecurityException("Parsing EcdsaPublicKey failed");
    }
  }

  @SuppressWarnings("UnusedException")
  private static EciesPrivateKey parsePrivateKey(
      ProtoKeySerialization serialization, @Nullable SecretKeyAccess access)
      throws GeneralSecurityException {
    if (!serialization.getTypeUrl().equals(PRIVATE_TYPE_URL)) {
      throw new IllegalArgumentException(
          "Wrong type URL in call to EciesProtoSerialization.parsePrivateKey: "
              + serialization.getTypeUrl());
    }
    try {
      com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey protoKey =
          com.google.crypto.tink.proto.EciesAeadHkdfPrivateKey.parseFrom(
              serialization.getValue(), ExtensionRegistryLite.getEmptyRegistry());
      if (protoKey.getVersion() != 0) {
        throw new GeneralSecurityException("Only version 0 keys are accepted");
      }
      com.google.crypto.tink.proto.EciesAeadHkdfPublicKey protoPublicKey = protoKey.getPublicKey();
      EciesParameters parameters =
          fromProtoParameters(serialization.getOutputPrefixType(), protoPublicKey.getParams());
      if (parameters.getCurveType().equals(EciesParameters.CurveType.X25519)) {
        EciesPublicKey publicKey =
            EciesPublicKey.createForCurveX25519(
                parameters,
                Bytes.copyFrom(protoPublicKey.getX().toByteArray()),
                serialization.getIdRequirementOrNull());
        return EciesPrivateKey.createForCurveX25519(
            publicKey,
            SecretBytes.copyFrom(
                protoKey.getKeyValue().toByteArray(), SecretKeyAccess.requireAccess(access)));
      }
      ECPoint point =
          new ECPoint(
              BigIntegerEncoding.fromUnsignedBigEndianBytes(protoPublicKey.getX().toByteArray()),
              BigIntegerEncoding.fromUnsignedBigEndianBytes(protoPublicKey.getY().toByteArray()));

      EciesPublicKey publicKey =
          EciesPublicKey.createForNistCurve(
              parameters, point, serialization.getIdRequirementOrNull());
      return EciesPrivateKey.createForNistCurve(
          publicKey,
          SecretBigInteger.fromBigInteger(
              BigIntegerEncoding.fromUnsignedBigEndianBytes(protoKey.getKeyValue().toByteArray()),
              SecretKeyAccess.requireAccess(access)));
    } catch (InvalidProtocolBufferException | IllegalArgumentException e) {
      throw new GeneralSecurityException("Parsing EcdsaPrivateKey failed");
    }
  }

  private EciesProtoSerialization() {}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy