diff options
author | ioannanedelcu <ioannanedelcu@google.com> | 2023-07-25 10:12:09 -0700 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2023-07-25 10:13:35 -0700 |
commit | 4216d80439ad3a176be17eab7ad490470f43a8d8 (patch) | |
tree | 82aa2a1dfa714f64c67147cbd012a3bf555b24e6 /java_src | |
parent | ed72a9b175ace4a4bc3bed7dc1d71087bb948493 (diff) | |
download | tink-4216d80439ad3a176be17eab7ad490470f43a8d8.tar.gz |
Add parsers and serializers for JwtRsaSsaPkcs1 parameters, public key and private key.
PiperOrigin-RevId: 550921147
Diffstat (limited to 'java_src')
9 files changed, 1152 insertions, 0 deletions
diff --git a/java_src/BUILD.bazel b/java_src/BUILD.bazel index 1900a28ae..d7bba6372 100644 --- a/java_src/BUILD.bazel +++ b/java_src/BUILD.bazel @@ -241,6 +241,7 @@ gen_maven_jar_rules( "//src/main/java/com/google/crypto/tink/jwt:jwt_public_key_verify_wrapper", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_parameters", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_private_key", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_proto_serialization", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_public_key", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_sign_key_manager", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_verify_key_manager", @@ -673,6 +674,7 @@ gen_maven_jar_rules( "//src/main/java/com/google/crypto/tink/jwt:jwt_public_key_verify_wrapper-android", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_parameters-android", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_private_key-android", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_proto_serialization-android", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_public_key-android", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_sign_key_manager-android", "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_verify_key_manager-android", diff --git a/java_src/src/main/java/com/google/crypto/tink/internal/testing/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/internal/testing/BUILD.bazel index 4a2b5ccc8..d0c534909 100644 --- a/java_src/src/main/java/com/google/crypto/tink/internal/testing/BUILD.bazel +++ b/java_src/src/main/java/com/google/crypto/tink/internal/testing/BUILD.bazel @@ -82,3 +82,13 @@ android_library( "@maven//:com_google_truth_truth", ], ) + +android_library( + name = "big_integer_test_util-android", + srcs = ["BigIntegerTestUtil.java"], +) + +java_library( + name = "big_integer_test_util", + srcs = ["BigIntegerTestUtil.java"], +) diff --git a/java_src/src/main/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtil.java b/java_src/src/main/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtil.java new file mode 100644 index 000000000..5ce9928bb --- /dev/null +++ b/java_src/src/main/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtil.java @@ -0,0 +1,39 @@ +// Copyright 2023 Google Inc. +// +// 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 specified language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.crypto.tink.internal.testing; + +/** Test helpers for BigInteger usage. */ +public final class BigIntegerTestUtil { + /** + * Adds a leading zero to the big-endian encoding, if necessary. + * + * <p>When encoding a BigInteger using `toBigEndianBytes()`, the minimal big-endian encoding uses + * the two's complement representation. This means that the encoding may have a leading zero. + */ + public static byte[] ensureLeadingZeroBit(byte[] minimalEncodedBigInteger) { + if (minimalEncodedBigInteger[0] < 0) { + // Add a leading zero to the encoding. + byte[] twosComplementEncoded = new byte[minimalEncodedBigInteger.length + 1]; + System.arraycopy( + minimalEncodedBigInteger, 0, twosComplementEncoded, 1, minimalEncodedBigInteger.length); + return twosComplementEncoded; + } + return minimalEncodedBigInteger; + } + + private BigIntegerTestUtil() {} +} diff --git a/java_src/src/main/java/com/google/crypto/tink/jwt/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/jwt/BUILD.bazel index b8ecbbd92..c8b3ee51d 100644 --- a/java_src/src/main/java/com/google/crypto/tink/jwt/BUILD.bazel +++ b/java_src/src/main/java/com/google/crypto/tink/jwt/BUILD.bazel @@ -1201,3 +1201,59 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +android_library( + name = "jwt_rsa_ssa_pkcs1_proto_serialization-android", + srcs = ["JwtRsaSsaPkcs1ProtoSerialization.java"], + deps = [ + ":jwt_rsa_ssa_pkcs1_parameters-android", + ":jwt_rsa_ssa_pkcs1_private_key-android", + ":jwt_rsa_ssa_pkcs1_public_key-android", + "//proto:jwt_rsa_ssa_pkcs1_java_proto_lite", + "//proto:tink_java_proto_lite", + "//src/main/java/com/google/crypto/tink:accesses_partial_key-android", + "//src/main/java/com/google/crypto/tink:secret_key_access-android", + "//src/main/java/com/google/crypto/tink/internal:big_integer_encoding-android", + "//src/main/java/com/google/crypto/tink/internal:enum_type_proto_converter-android", + "//src/main/java/com/google/crypto/tink/internal:key_parser-android", + "//src/main/java/com/google/crypto/tink/internal:key_serializer-android", + "//src/main/java/com/google/crypto/tink/internal:mutable_serialization_registry-android", + "//src/main/java/com/google/crypto/tink/internal:parameters_parser-android", + "//src/main/java/com/google/crypto/tink/internal:parameters_serializer-android", + "//src/main/java/com/google/crypto/tink/internal:proto_key_serialization-android", + "//src/main/java/com/google/crypto/tink/internal:proto_parameters_serialization-android", + "//src/main/java/com/google/crypto/tink/internal:util-android", + "//src/main/java/com/google/crypto/tink/util:bytes-android", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer-android", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_protobuf_protobuf_javalite", + ], +) + +java_library( + name = "jwt_rsa_ssa_pkcs1_proto_serialization", + srcs = ["JwtRsaSsaPkcs1ProtoSerialization.java"], + deps = [ + ":jwt_rsa_ssa_pkcs1_parameters", + ":jwt_rsa_ssa_pkcs1_private_key", + ":jwt_rsa_ssa_pkcs1_public_key", + "//proto:jwt_rsa_ssa_pkcs1_java_proto", + "//proto:tink_java_proto", + "//src/main/java/com/google/crypto/tink:accesses_partial_key", + "//src/main/java/com/google/crypto/tink:secret_key_access", + "//src/main/java/com/google/crypto/tink/internal:big_integer_encoding", + "//src/main/java/com/google/crypto/tink/internal:enum_type_proto_converter", + "//src/main/java/com/google/crypto/tink/internal:key_parser", + "//src/main/java/com/google/crypto/tink/internal:key_serializer", + "//src/main/java/com/google/crypto/tink/internal:mutable_serialization_registry", + "//src/main/java/com/google/crypto/tink/internal:parameters_parser", + "//src/main/java/com/google/crypto/tink/internal:parameters_serializer", + "//src/main/java/com/google/crypto/tink/internal:proto_key_serialization", + "//src/main/java/com/google/crypto/tink/internal:proto_parameters_serialization", + "//src/main/java/com/google/crypto/tink/internal:util", + "//src/main/java/com/google/crypto/tink/util:bytes", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer", + "@maven//:com_google_code_findbugs_jsr305", + "@maven//:com_google_protobuf_protobuf_java", + ], +) diff --git a/java_src/src/main/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerialization.java b/java_src/src/main/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerialization.java new file mode 100644 index 000000000..9c54b9f94 --- /dev/null +++ b/java_src/src/main/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerialization.java @@ -0,0 +1,372 @@ +// 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.jwt; + +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.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.JwtRsaSsaPkcs1Algorithm; +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.protobuf.ByteString; +import com.google.protobuf.ExtensionRegistryLite; +import com.google.protobuf.InvalidProtocolBufferException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import javax.annotation.Nullable; + +/** + * Methods to serialize and parse {@link JwtRsaSsaPkcs1PrivateKey} and {@link + * JwtRsaSsaPkcs1PublicKey} objects and {@link JwtRsaSsaPkcs1Parameters} objects. + */ +@AccessesPartialKey +@SuppressWarnings("UnnecessarilyFullyQualified") // Fully specifying proto types is more readable +final class JwtRsaSsaPkcs1ProtoSerialization { + private static final String PRIVATE_TYPE_URL = + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey"; + 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.JwtRsaSsaPkcs1PublicKey"; + private static final Bytes PUBLIC_TYPE_URL_BYTES = toBytesFromPrintableAscii(PUBLIC_TYPE_URL); + + private static final ParametersSerializer<JwtRsaSsaPkcs1Parameters, ProtoParametersSerialization> + PARAMETERS_SERIALIZER = + ParametersSerializer.create( + JwtRsaSsaPkcs1ProtoSerialization::serializeParameters, + JwtRsaSsaPkcs1Parameters.class, + ProtoParametersSerialization.class); + + private static final ParametersParser<ProtoParametersSerialization> PARAMETERS_PARSER = + ParametersParser.create( + JwtRsaSsaPkcs1ProtoSerialization::parseParameters, + PRIVATE_TYPE_URL_BYTES, + ProtoParametersSerialization.class); + + private static final KeySerializer<JwtRsaSsaPkcs1PublicKey, ProtoKeySerialization> + PUBLIC_KEY_SERIALIZER = + KeySerializer.create( + JwtRsaSsaPkcs1ProtoSerialization::serializePublicKey, + JwtRsaSsaPkcs1PublicKey.class, + ProtoKeySerialization.class); + + private static final KeyParser<ProtoKeySerialization> PUBLIC_KEY_PARSER = + KeyParser.create( + JwtRsaSsaPkcs1ProtoSerialization::parsePublicKey, + PUBLIC_TYPE_URL_BYTES, + ProtoKeySerialization.class); + + private static final KeySerializer<JwtRsaSsaPkcs1PrivateKey, ProtoKeySerialization> + PRIVATE_KEY_SERIALIZER = + KeySerializer.create( + JwtRsaSsaPkcs1ProtoSerialization::serializePrivateKey, + JwtRsaSsaPkcs1PrivateKey.class, + ProtoKeySerialization.class); + + private static final KeyParser<ProtoKeySerialization> PRIVATE_KEY_PARSER = + KeyParser.create( + JwtRsaSsaPkcs1ProtoSerialization::parsePrivateKey, + PRIVATE_TYPE_URL_BYTES, + ProtoKeySerialization.class); + + private static final EnumTypeProtoConverter< + JwtRsaSsaPkcs1Algorithm, JwtRsaSsaPkcs1Parameters.Algorithm> + ALGORITHM_CONVERTER = + EnumTypeProtoConverter + .<JwtRsaSsaPkcs1Algorithm, JwtRsaSsaPkcs1Parameters.Algorithm>builder() + .add(JwtRsaSsaPkcs1Algorithm.RS256, JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .add(JwtRsaSsaPkcs1Algorithm.RS384, JwtRsaSsaPkcs1Parameters.Algorithm.RS384) + .add(JwtRsaSsaPkcs1Algorithm.RS512, JwtRsaSsaPkcs1Parameters.Algorithm.RS512) + .build(); + + private static OutputPrefixType toProtoOutputPrefixType(JwtRsaSsaPkcs1Parameters parameters) { + if (parameters + .getKidStrategy() + .equals(JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID)) { + return OutputPrefixType.TINK; + } + return OutputPrefixType.RAW; + } + + /** Encodes a BigInteger using a big-endian encoding. */ + private static ByteString encodeBigInteger(BigInteger i) { + // Note that toBigEndianBytes() returns the minimal big-endian encoding using the two's + // complement representation. This means that the encoding may have a leading zero. + byte[] encoded = BigIntegerEncoding.toBigEndianBytes(i); + return ByteString.copyFrom(encoded); + } + + private static com.google.crypto.tink.proto.JwtRsaSsaPkcs1KeyFormat getProtoKeyFormat( + JwtRsaSsaPkcs1Parameters parameters) throws GeneralSecurityException { + if (!parameters.getKidStrategy().equals(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED) + && !parameters + .getKidStrategy() + .equals(JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID)) { + throw new GeneralSecurityException( + "Unable to serialize Parameters object with KidStrategy " + parameters.getKidStrategy()); + } + return com.google.crypto.tink.proto.JwtRsaSsaPkcs1KeyFormat.newBuilder() + .setVersion(0) + .setAlgorithm(ALGORITHM_CONVERTER.toProtoEnum(parameters.getAlgorithm())) + .setModulusSizeInBits(parameters.getModulusSizeBits()) + .setPublicExponent(encodeBigInteger(parameters.getPublicExponent())) + .build(); + } + + private static ProtoParametersSerialization serializeParameters( + JwtRsaSsaPkcs1Parameters parameters) throws GeneralSecurityException { + OutputPrefixType outputPrefixType = toProtoOutputPrefixType(parameters); + return ProtoParametersSerialization.create( + KeyTemplate.newBuilder() + .setTypeUrl(PRIVATE_TYPE_URL) + .setValue(getProtoKeyFormat(parameters).toByteString()) + .setOutputPrefixType(outputPrefixType) + .build()); + } + + private static com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey getProtoPublicKey( + JwtRsaSsaPkcs1PublicKey key) throws GeneralSecurityException { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.Builder builder = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(ALGORITHM_CONVERTER.toProtoEnum(key.getParameters().getAlgorithm())) + .setN(encodeBigInteger(key.getModulus())) + .setE(encodeBigInteger(key.getParameters().getPublicExponent())); + if (key.getParameters().getKidStrategy().equals(JwtRsaSsaPkcs1Parameters.KidStrategy.CUSTOM)) { + builder.setCustomKid( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.CustomKid.newBuilder() + .setValue(key.getKid().get()) + .build()); + } + return builder.build(); + } + + private static ProtoKeySerialization serializePublicKey( + JwtRsaSsaPkcs1PublicKey key, @Nullable SecretKeyAccess access) + throws GeneralSecurityException { + return ProtoKeySerialization.create( + PUBLIC_TYPE_URL, + getProtoPublicKey(key).toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + toProtoOutputPrefixType(key.getParameters()), + key.getIdRequirementOrNull()); + } + + private static ByteString encodeSecretBigInteger(SecretBigInteger i, SecretKeyAccess access) { + return encodeBigInteger(i.getBigInteger(access)); + } + + private static ProtoKeySerialization serializePrivateKey( + JwtRsaSsaPkcs1PrivateKey key, @Nullable SecretKeyAccess access) + throws GeneralSecurityException { + SecretKeyAccess a = SecretKeyAccess.requireAccess(access); + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey protoPrivateKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.newBuilder() + .setVersion(0) + .setPublicKey(getProtoPublicKey(key.getPublicKey())) + .setD(encodeSecretBigInteger(key.getPrivateExponent(), a)) + .setP(encodeSecretBigInteger(key.getPrimeP(), a)) + .setQ(encodeSecretBigInteger(key.getPrimeQ(), a)) + .setDp(encodeSecretBigInteger(key.getPrimeExponentP(), a)) + .setDq(encodeSecretBigInteger(key.getPrimeExponentQ(), a)) + .setCrt(encodeSecretBigInteger(key.getCrtCoefficient(), a)) + .build(); + return ProtoKeySerialization.create( + PRIVATE_TYPE_URL, + protoPrivateKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PRIVATE, + toProtoOutputPrefixType(key.getParameters()), + key.getIdRequirementOrNull()); + } + + private static BigInteger decodeBigInteger(ByteString data) { + return BigIntegerEncoding.fromUnsignedBigEndianBytes(data.toByteArray()); + } + + private static void validateVersion(int version) throws GeneralSecurityException { + if (version != 0) { + throw new GeneralSecurityException("Parsing failed: unknown version " + version); + } + } + + private static JwtRsaSsaPkcs1Parameters parseParameters( + ProtoParametersSerialization serialization) throws GeneralSecurityException { + if (!serialization.getKeyTemplate().getTypeUrl().equals(PRIVATE_TYPE_URL)) { + throw new IllegalArgumentException( + "Wrong type URL in call to JwtRsaSsaPkcs1ProtoSerialization.parseParameters: " + + serialization.getKeyTemplate().getTypeUrl()); + } + com.google.crypto.tink.proto.JwtRsaSsaPkcs1KeyFormat format; + try { + format = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1KeyFormat.parseFrom( + serialization.getKeyTemplate().getValue(), ExtensionRegistryLite.getEmptyRegistry()); + } catch (InvalidProtocolBufferException e) { + throw new GeneralSecurityException("Parsing JwtRsaSsaPkcs1Parameters failed: ", e); + } + validateVersion(format.getVersion()); + JwtRsaSsaPkcs1Parameters.KidStrategy kidStrategy = null; + if (serialization.getKeyTemplate().getOutputPrefixType().equals(OutputPrefixType.TINK)) { + kidStrategy = JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID; + } + if (serialization.getKeyTemplate().getOutputPrefixType().equals(OutputPrefixType.RAW)) { + kidStrategy = JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED; + } + if (kidStrategy == null) { + throw new GeneralSecurityException("Invalid OutputPrefixType for JwtHmacKeyFormat"); + } + return JwtRsaSsaPkcs1Parameters.builder() + .setKidStrategy(kidStrategy) + .setAlgorithm(ALGORITHM_CONVERTER.fromProtoEnum(format.getAlgorithm())) + .setPublicExponent(decodeBigInteger(format.getPublicExponent())) + .setModulusSizeBits(format.getModulusSizeInBits()) + .build(); + } + + private static JwtRsaSsaPkcs1PublicKey getPublicKeyFromProto( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoKey, + OutputPrefixType outputPrefixType, + @Nullable Integer idRequirement) + throws GeneralSecurityException { + validateVersion(protoKey.getVersion()); + + JwtRsaSsaPkcs1Parameters.Builder parametersBuilder = JwtRsaSsaPkcs1Parameters.builder(); + JwtRsaSsaPkcs1PublicKey.Builder keyBuilder = JwtRsaSsaPkcs1PublicKey.builder(); + + if (outputPrefixType.equals(OutputPrefixType.TINK)) { + if (protoKey.hasCustomKid()) { + throw new GeneralSecurityException( + "Keys serialized with OutputPrefixType TINK should not have a custom kid"); + } + if (idRequirement == null) { + throw new GeneralSecurityException( + "Keys serialized with OutputPrefixType TINK need an ID Requirement"); + } + parametersBuilder.setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID); + keyBuilder.setIdRequirement(idRequirement); + } else if (outputPrefixType.equals(OutputPrefixType.RAW)) { + if (protoKey.hasCustomKid()) { + parametersBuilder.setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.CUSTOM); + keyBuilder.setCustomKid(protoKey.getCustomKid().getValue()); + } else { + parametersBuilder.setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED); + } + } + + BigInteger modulus = decodeBigInteger(protoKey.getN()); + int modulusSizeInBits = modulus.bitLength(); + parametersBuilder + .setAlgorithm(ALGORITHM_CONVERTER.fromProtoEnum(protoKey.getAlgorithm())) + .setPublicExponent(decodeBigInteger(protoKey.getE())) + .setModulusSizeBits(modulusSizeInBits); + + keyBuilder.setModulus(modulus).setParameters(parametersBuilder.build()); + + return keyBuilder.build(); + } + + @SuppressWarnings("UnusedException") + private static JwtRsaSsaPkcs1PublicKey parsePublicKey( + ProtoKeySerialization serialization, @Nullable SecretKeyAccess access) + throws GeneralSecurityException { + if (!serialization.getTypeUrl().equals(PUBLIC_TYPE_URL)) { + throw new IllegalArgumentException( + "Wrong type URL in call to JwtRsaSsaPkcs1ProtoSerialization.parsePublicKey: " + + serialization.getTypeUrl()); + } + try { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.parseFrom( + serialization.getValue(), ExtensionRegistryLite.getEmptyRegistry()); + return getPublicKeyFromProto( + protoKey, serialization.getOutputPrefixType(), serialization.getIdRequirementOrNull()); + } catch (InvalidProtocolBufferException e) { + throw new GeneralSecurityException("Parsing JwtRsaSsaPkcs1PublicKey failed"); + } + } + + private static SecretBigInteger decodeSecretBigInteger(ByteString data, SecretKeyAccess access) { + return SecretBigInteger.fromBigInteger( + BigIntegerEncoding.fromUnsignedBigEndianBytes(data.toByteArray()), access); + } + + @SuppressWarnings("UnusedException") + private static JwtRsaSsaPkcs1PrivateKey parsePrivateKey( + ProtoKeySerialization serialization, @Nullable SecretKeyAccess access) + throws GeneralSecurityException { + if (!serialization.getTypeUrl().equals(PRIVATE_TYPE_URL)) { + throw new IllegalArgumentException( + "Wrong type URL in call to JwtRsaSsaPkcs1ProtoSerialization.parsePrivateKey: " + + serialization.getTypeUrl()); + } + try { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey protoKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.parseFrom( + serialization.getValue(), ExtensionRegistryLite.getEmptyRegistry()); + validateVersion(protoKey.getVersion()); + + JwtRsaSsaPkcs1PublicKey publicKey = + getPublicKeyFromProto( + protoKey.getPublicKey(), + serialization.getOutputPrefixType(), + serialization.getIdRequirementOrNull()); + + SecretKeyAccess a = SecretKeyAccess.requireAccess(access); + return JwtRsaSsaPkcs1PrivateKey.builder() + .setPublicKey(publicKey) + .setPrimes( + decodeSecretBigInteger(protoKey.getP(), a), + decodeSecretBigInteger(protoKey.getQ(), a)) + .setPrivateExponent(decodeSecretBigInteger(protoKey.getD(), a)) + .setPrimeExponents( + decodeSecretBigInteger(protoKey.getDp(), a), + decodeSecretBigInteger(protoKey.getDq(), a)) + .setCrtCoefficient(decodeSecretBigInteger(protoKey.getCrt(), a)) + .build(); + } catch (InvalidProtocolBufferException e) { + throw new GeneralSecurityException("Parsing JwtRsaSsaPkcs1PrivateKey failed"); + } + } + + public static void register() throws GeneralSecurityException { + register(MutableSerializationRegistry.globalInstance()); + } + + 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 JwtRsaSsaPkcs1ProtoSerialization() {} +} diff --git a/java_src/src/test/java/com/google/crypto/tink/internal/testing/BUILD.bazel b/java_src/src/test/java/com/google/crypto/tink/internal/testing/BUILD.bazel index 012df0e0e..6e62c513a 100644 --- a/java_src/src/test/java/com/google/crypto/tink/internal/testing/BUILD.bazel +++ b/java_src/src/test/java/com/google/crypto/tink/internal/testing/BUILD.bazel @@ -68,3 +68,14 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "BigIntegerTestUtilTest", + size = "small", + srcs = ["BigIntegerTestUtilTest.java"], + deps = [ + "//src/main/java/com/google/crypto/tink/internal/testing:big_integer_test_util", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/java_src/src/test/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtilTest.java b/java_src/src/test/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtilTest.java new file mode 100644 index 000000000..726d77e87 --- /dev/null +++ b/java_src/src/test/java/com/google/crypto/tink/internal/testing/BigIntegerTestUtilTest.java @@ -0,0 +1,46 @@ +// 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.internal.testing; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class BigIntegerTestUtilTest { + + @Test + public void ensureLeadingZeroBit_bitNotSet_works() throws Exception { + // 258 = 1 * 256 + 2. + // If the most significant bit is not set, there is no leading zero. + byte[] encodingOf258 = new byte[] {(byte) 1, (byte) 2}; + + assertThat(BigIntegerTestUtil.ensureLeadingZeroBit(encodingOf258)).isEqualTo(encodingOf258); + } + + @Test + public void ensureLeadingZeroBit_bitSet_works() throws Exception { + // If the most significant bit is set, then a leading zero is added. + byte[] encodingOf255 = new byte[] {(byte) 0xff}; + byte[] twoComplementEncodingOf255 = new byte[] {(byte) 0, (byte) 0xff}; + + assertThat(BigIntegerTestUtil.ensureLeadingZeroBit(encodingOf255)) + .isEqualTo(twoComplementEncodingOf255); + } +} diff --git a/java_src/src/test/java/com/google/crypto/tink/jwt/BUILD.bazel b/java_src/src/test/java/com/google/crypto/tink/jwt/BUILD.bazel index 79e027604..58d80f6a0 100644 --- a/java_src/src/test/java/com/google/crypto/tink/jwt/BUILD.bazel +++ b/java_src/src/test/java/com/google/crypto/tink/jwt/BUILD.bazel @@ -543,3 +543,31 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "JwtRsaSsaPkcs1ProtoSerializationTest", + size = "small", + srcs = ["JwtRsaSsaPkcs1ProtoSerializationTest.java"], + deps = [ + "//proto:jwt_rsa_ssa_pkcs1_java_proto", + "//proto:tink_java_proto", + "//src/main/java/com/google/crypto/tink:insecure_secret_key_access", + "//src/main/java/com/google/crypto/tink:key", + "//src/main/java/com/google/crypto/tink:parameters", + "//src/main/java/com/google/crypto/tink/internal:big_integer_encoding", + "//src/main/java/com/google/crypto/tink/internal:mutable_serialization_registry", + "//src/main/java/com/google/crypto/tink/internal:proto_key_serialization", + "//src/main/java/com/google/crypto/tink/internal:proto_parameters_serialization", + "//src/main/java/com/google/crypto/tink/internal/testing:asserts", + "//src/main/java/com/google/crypto/tink/internal/testing:big_integer_test_util", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_parameters", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_private_key", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_proto_serialization", + "//src/main/java/com/google/crypto/tink/jwt:jwt_rsa_ssa_pkcs1_public_key", + "//src/main/java/com/google/crypto/tink/subtle:base64", + "//src/main/java/com/google/crypto/tink/util:secret_big_integer", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/java_src/src/test/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerializationTest.java b/java_src/src/test/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerializationTest.java new file mode 100644 index 000000000..6d654d613 --- /dev/null +++ b/java_src/src/test/java/com/google/crypto/tink/jwt/JwtRsaSsaPkcs1ProtoSerializationTest.java @@ -0,0 +1,588 @@ +// 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.jwt; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.crypto.tink.internal.testing.Asserts.assertEqualWhenValueParsed; +import static org.junit.Assert.assertThrows; + +import com.google.crypto.tink.InsecureSecretKeyAccess; +import com.google.crypto.tink.Key; +import com.google.crypto.tink.Parameters; +import com.google.crypto.tink.internal.BigIntegerEncoding; +import com.google.crypto.tink.internal.MutableSerializationRegistry; +import com.google.crypto.tink.internal.ProtoKeySerialization; +import com.google.crypto.tink.internal.ProtoParametersSerialization; +import com.google.crypto.tink.internal.testing.BigIntegerTestUtil; +import com.google.crypto.tink.proto.JwtRsaSsaPkcs1Algorithm; +import com.google.crypto.tink.proto.JwtRsaSsaPkcs1KeyFormat; +import com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.CustomKid; +import com.google.crypto.tink.proto.KeyData.KeyMaterialType; +import com.google.crypto.tink.proto.OutputPrefixType; +import com.google.crypto.tink.subtle.Base64; +import com.google.crypto.tink.util.SecretBigInteger; +import com.google.protobuf.ByteString; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.FromDataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +@RunWith(Theories.class) +public final class JwtRsaSsaPkcs1ProtoSerializationTest { + private static final String TYPE_URL = + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey"; + + // Test vector from https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2 + static final byte[] EXPONENT_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit(Base64.urlSafeDecode("AQAB")); + static final BigInteger EXPONENT = new BigInteger(1, EXPONENT_BYTES); + static final byte[] MODULUS_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx" + + "HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs" + + "D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH" + + "SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV" + + "MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8" + + "NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ")); + static final BigInteger MODULUS = new BigInteger(1, MODULUS_BYTES); + static final byte[] P_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi" + + "YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG" + + "BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc")); + static final BigInteger P = new BigInteger(1, P_BYTES); + static final byte[] Q_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa" + + "ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA" + + "-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc")); + static final BigInteger Q = new BigInteger(1, Q_BYTES); + static final byte[] D_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I" + + "jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0" + + "BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn" + + "439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT" + + "CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh" + + "BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ")); + static final BigInteger D = new BigInteger(1, D_BYTES); + static final byte[] DP_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q" + + "CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb" + + "34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0")); + static final BigInteger DP = new BigInteger(1, DP_BYTES); + static final byte[] DQ_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa" + + "7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky" + + "NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU")); + static final BigInteger DQ = new BigInteger(1, DQ_BYTES); + static final byte[] Q_INV_BYTES = + BigIntegerTestUtil.ensureLeadingZeroBit( + Base64.urlSafeDecode( + "IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o" + + "y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU" + + "W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U")); + static final BigInteger Q_INV = new BigInteger(1, Q_INV_BYTES); + + private static final MutableSerializationRegistry registry = new MutableSerializationRegistry(); + + private static final class AlgorithmTuple { + final JwtRsaSsaPkcs1Parameters.Algorithm algorithm; + final JwtRsaSsaPkcs1Algorithm protoAlgorithm; + + AlgorithmTuple( + JwtRsaSsaPkcs1Parameters.Algorithm algorithm, JwtRsaSsaPkcs1Algorithm protoAlgorithm) { + this.algorithm = algorithm; + this.protoAlgorithm = protoAlgorithm; + } + } + + @DataPoints("algorithms") + public static final AlgorithmTuple[] ALGORITHMS = + new AlgorithmTuple[] { + new AlgorithmTuple(JwtRsaSsaPkcs1Parameters.Algorithm.RS256, JwtRsaSsaPkcs1Algorithm.RS256), + new AlgorithmTuple(JwtRsaSsaPkcs1Parameters.Algorithm.RS384, JwtRsaSsaPkcs1Algorithm.RS384), + new AlgorithmTuple(JwtRsaSsaPkcs1Parameters.Algorithm.RS512, JwtRsaSsaPkcs1Algorithm.RS512), + }; + + @BeforeClass + public static void setUp() throws Exception { + JwtRsaSsaPkcs1ProtoSerialization.register(registry); + } + + @Theory + public void serializeParseParameters_kidStrategyIgnored_works( + @FromDataPoints("algorithms") AlgorithmTuple algorithmTuple) throws Exception { + JwtRsaSsaPkcs1Parameters parameters = + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED) + .setAlgorithm(algorithmTuple.algorithm) + .build(); + ProtoParametersSerialization serialization = + ProtoParametersSerialization.create( + TYPE_URL, + OutputPrefixType.RAW, + JwtRsaSsaPkcs1KeyFormat.newBuilder() + .setVersion(0) + .setAlgorithm(algorithmTuple.protoAlgorithm) + .setModulusSizeInBits(2048) + .setPublicExponent( + ByteString.copyFrom( + BigIntegerEncoding.toBigEndianBytes(BigInteger.valueOf(65537)))) + .build()); + + ProtoParametersSerialization serialized = + registry.serializeParameters(parameters, ProtoParametersSerialization.class); + assertEqualWhenValueParsed(JwtRsaSsaPkcs1KeyFormat.parser(), serialized, serialization); + + Parameters parsed = registry.parseParameters(serialization); + assertThat(parsed).isEqualTo(parameters); + } + + @Test + public void serializeParseParameters_kidStrategyBase64_works() throws Exception { + JwtRsaSsaPkcs1Parameters parameters = + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(EXPONENT) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .build(); + ProtoParametersSerialization serialization = + ProtoParametersSerialization.create( + TYPE_URL, + OutputPrefixType.TINK, + JwtRsaSsaPkcs1KeyFormat.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setModulusSizeInBits(2048) + .setPublicExponent(ByteString.copyFrom(EXPONENT_BYTES)) + .build()); + + ProtoParametersSerialization serialized = + registry.serializeParameters(parameters, ProtoParametersSerialization.class); + assertEqualWhenValueParsed(JwtRsaSsaPkcs1KeyFormat.parser(), serialized, serialization); + + Parameters parsed = registry.parseParameters(serialization); + assertThat(parsed).isEqualTo(parameters); + } + + @Test + public void serializeParameters_kidStrategyCustom_cannotBeSerialized_throws() throws Exception { + JwtRsaSsaPkcs1Parameters parameters = + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(EXPONENT) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.CUSTOM) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS512) + .build(); + assertThrows( + GeneralSecurityException.class, + () -> registry.serializeParameters(parameters, ProtoParametersSerialization.class)); + } + + @Test + public void parseParameters_crunchy_cannotBeParsed_throws() throws Exception { + ProtoParametersSerialization serialization = + ProtoParametersSerialization.create( + TYPE_URL, + OutputPrefixType.CRUNCHY, + JwtRsaSsaPkcs1KeyFormat.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS512) + .setModulusSizeInBits(2048) + .setPublicExponent(ByteString.copyFrom(EXPONENT_BYTES)) + .build()); + assertThrows(GeneralSecurityException.class, () -> registry.parseParameters(serialization)); + } + + @Theory + public void serializeParsePublicKey_kidIgnored_equal( + @FromDataPoints("algorithms") AlgorithmTuple algorithmTuple) throws Exception { + JwtRsaSsaPkcs1PublicKey key = + JwtRsaSsaPkcs1PublicKey.builder() + .setParameters( + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED) + .setAlgorithm(algorithmTuple.algorithm) + .build()) + .setModulus(MODULUS) + .build(); + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(algorithmTuple.protoAlgorithm) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.RAW, + /* idRequirement= */ null); + + Key parsed = registry.parseKey(serialization, /* access= */ null); + assertThat(parsed.equalsKey(key)).isTrue(); + + ProtoKeySerialization serialized = + registry.serializeKey(key, ProtoKeySerialization.class, /* access= */ null); + assertEqualWhenValueParsed( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.parser(), serialized, serialization); + } + + @Test + public void serializeParsePublicKey_kidCustom_equal() throws Exception { + JwtRsaSsaPkcs1PublicKey key = + JwtRsaSsaPkcs1PublicKey.builder() + .setParameters( + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.CUSTOM) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .build()) + .setModulus(MODULUS) + .setCustomKid("myCustomKid") + .build(); + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .setCustomKid(CustomKid.newBuilder().setValue("myCustomKid").build()) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.RAW, + /* idRequirement= */ null); + + Key parsed = registry.parseKey(serialization, /* access= */ null); + assertThat(parsed.equalsKey(key)).isTrue(); + + ProtoKeySerialization serialized = + registry.serializeKey(key, ProtoKeySerialization.class, /* access= */ null); + assertEqualWhenValueParsed( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.parser(), serialized, serialization); + } + + @Test + public void serializeParsePublicKey_base64Kid_equal() throws Exception { + JwtRsaSsaPkcs1PublicKey key = + JwtRsaSsaPkcs1PublicKey.builder() + .setParameters( + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.BASE64_ENCODED_KEY_ID) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .build()) + .setModulus(MODULUS) + .setIdRequirement(12345) + .build(); + + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.TINK, + /* idRequirement= */ 12345); + + Key parsed = registry.parseKey(serialization, /* access= */ null); + assertThat(parsed.equalsKey(key)).isTrue(); + + ProtoKeySerialization serialized = + registry.serializeKey(key, ProtoKeySerialization.class, /* access= */ null); + assertEqualWhenValueParsed( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.parser(), serialized, serialization); + } + + @Test + public void parsePublicKey_crunchy_cannotBeParsed_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.CRUNCHY, + /* idRequirement= */ 12345); + + assertThrows( + GeneralSecurityException.class, () -> registry.parseKey(serialization, /* access= */ null)); + } + + @Test + public void parsePublicKey_tinkAndCustomKeyId_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .setCustomKid(CustomKid.newBuilder().setValue("WillNotParseWithTINK").build()) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.TINK, + /* idRequirement= */ 12345); + + assertThrows( + GeneralSecurityException.class, () -> registry.parseKey(serialization, /* access= */ null)); + } + + @Test + public void parsePublicKey_wrongVersion_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(1) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.TINK, + /* idRequirement= */ 12345); + + assertThrows( + GeneralSecurityException.class, () -> registry.parseKey(serialization, /* access= */ null)); + } + + @Test + public void parsePublicKey_unknownAlgorithm_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey protoPublicKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(1) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS_UNKNOWN) + .build(); + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey", + protoPublicKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PUBLIC, + OutputPrefixType.TINK, + /* idRequirement= */ 12345); + + assertThrows( + GeneralSecurityException.class, () -> registry.parseKey(serialization, /* access= */ null)); + } + + @Test + public void serializeParsePrivateKey_kidIgnored_equal() throws Exception { + JwtRsaSsaPkcs1PublicKey publicKey = + JwtRsaSsaPkcs1PublicKey.builder() + .setParameters( + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .build()) + .setModulus(MODULUS) + .build(); + JwtRsaSsaPkcs1PrivateKey privateKey = + JwtRsaSsaPkcs1PrivateKey.builder() + .setPublicKey(publicKey) + .setPrimes( + SecretBigInteger.fromBigInteger(P, InsecureSecretKeyAccess.get()), + SecretBigInteger.fromBigInteger(Q, InsecureSecretKeyAccess.get())) + .setPrivateExponent(SecretBigInteger.fromBigInteger(D, InsecureSecretKeyAccess.get())) + .setPrimeExponents( + SecretBigInteger.fromBigInteger(DP, InsecureSecretKeyAccess.get()), + SecretBigInteger.fromBigInteger(DQ, InsecureSecretKeyAccess.get())) + .setCrtCoefficient( + SecretBigInteger.fromBigInteger(Q_INV, InsecureSecretKeyAccess.get())) + .build(); + + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey protoPrivateKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.newBuilder() + .setVersion(0) + .setPublicKey( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build()) + .setD(ByteString.copyFrom(D_BYTES)) + .setP(ByteString.copyFrom(P_BYTES)) + .setQ(ByteString.copyFrom(Q_BYTES)) + .setDp(ByteString.copyFrom(DP_BYTES)) + .setDq(ByteString.copyFrom(DQ_BYTES)) + .setCrt(ByteString.copyFrom(Q_INV_BYTES)) + .build(); + + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey", + protoPrivateKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PRIVATE, + OutputPrefixType.RAW, + /* idRequirement= */ null); + + Key parsed = registry.parseKey(serialization, InsecureSecretKeyAccess.get()); + assertThat(parsed.equalsKey(privateKey)).isTrue(); + + ProtoKeySerialization serialized = + registry.serializeKey( + privateKey, ProtoKeySerialization.class, InsecureSecretKeyAccess.get()); + assertEqualWhenValueParsed( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.parser(), serialized, serialization); + } + + @Test + public void parsePrivateKey_invalidVersion_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey protoPrivateKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.newBuilder() + .setVersion(1) + .setPublicKey( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build()) + .setD(ByteString.copyFrom(D_BYTES)) + .setP(ByteString.copyFrom(P_BYTES)) + .setQ(ByteString.copyFrom(Q_BYTES)) + .setDp(ByteString.copyFrom(DP_BYTES)) + .setDq(ByteString.copyFrom(DQ_BYTES)) + .setCrt(ByteString.copyFrom(Q_INV_BYTES)) + .build(); + + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey", + protoPrivateKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PRIVATE, + OutputPrefixType.RAW, + /* idRequirement= */ null); + + assertThrows( + GeneralSecurityException.class, + () -> registry.parseKey(serialization, InsecureSecretKeyAccess.get())); + } + + @Test + public void serializePrivateKey_noSecretKeyAccess_throws() throws Exception { + JwtRsaSsaPkcs1PublicKey publicKey = + JwtRsaSsaPkcs1PublicKey.builder() + .setParameters( + JwtRsaSsaPkcs1Parameters.builder() + .setModulusSizeBits(2048) + .setPublicExponent(JwtRsaSsaPkcs1Parameters.F4) + .setKidStrategy(JwtRsaSsaPkcs1Parameters.KidStrategy.IGNORED) + .setAlgorithm(JwtRsaSsaPkcs1Parameters.Algorithm.RS256) + .build()) + .setModulus(MODULUS) + .build(); + JwtRsaSsaPkcs1PrivateKey privateKey = + JwtRsaSsaPkcs1PrivateKey.builder() + .setPublicKey(publicKey) + .setPrimes( + SecretBigInteger.fromBigInteger(P, InsecureSecretKeyAccess.get()), + SecretBigInteger.fromBigInteger(Q, InsecureSecretKeyAccess.get())) + .setPrivateExponent(SecretBigInteger.fromBigInteger(D, InsecureSecretKeyAccess.get())) + .setPrimeExponents( + SecretBigInteger.fromBigInteger(DP, InsecureSecretKeyAccess.get()), + SecretBigInteger.fromBigInteger(DQ, InsecureSecretKeyAccess.get())) + .setCrtCoefficient( + SecretBigInteger.fromBigInteger(Q_INV, InsecureSecretKeyAccess.get())) + .build(); + + assertThrows( + GeneralSecurityException.class, + () -> registry.serializeKey(privateKey, ProtoKeySerialization.class, /* access= */ null)); + } + + @Test + public void parsePrivateKey_noSecretKeyAccess_throws() throws Exception { + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey protoPrivateKey = + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PrivateKey.newBuilder() + .setVersion(1) + .setPublicKey( + com.google.crypto.tink.proto.JwtRsaSsaPkcs1PublicKey.newBuilder() + .setVersion(0) + .setAlgorithm(JwtRsaSsaPkcs1Algorithm.RS256) + .setN(ByteString.copyFrom(MODULUS_BYTES)) + .setE(ByteString.copyFrom(EXPONENT_BYTES)) + .build()) + .setD(ByteString.copyFrom(D_BYTES)) + .setP(ByteString.copyFrom(P_BYTES)) + .setQ(ByteString.copyFrom(Q_BYTES)) + .setDp(ByteString.copyFrom(DP_BYTES)) + .setDq(ByteString.copyFrom(DQ_BYTES)) + .setCrt(ByteString.copyFrom(Q_INV_BYTES)) + .build(); + + ProtoKeySerialization serialization = + ProtoKeySerialization.create( + "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey", + protoPrivateKey.toByteString(), + KeyMaterialType.ASYMMETRIC_PRIVATE, + OutputPrefixType.RAW, + /* idRequirement= */ null); + + assertThrows( + GeneralSecurityException.class, () -> registry.parseKey(serialization, /* access= */ null)); + } +} |