| // Copyright 2017 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 specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package com.google.crypto.tink.subtle; |
| |
| import com.google.crypto.tink.subtle.Enums.HashType; |
| import java.io.File; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.util.Locale; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Validation helper methods. |
| * |
| * @since 1.0.0 |
| */ |
| public final class Validators { |
| private Validators() {} |
| |
| private static final String TYPE_URL_PREFIX = "type.googleapis.com/"; |
| /** |
| * To reach 128-bit security strength, RSA's modulus must be at least 3072-bit while 2048-bit RSA |
| * key only has 112-bit security. Nevertheless, a 2048-bit RSA key is considered safe by NIST |
| * until 2030 (see https://www.keylength.com/en/4/). |
| */ |
| private static final int MIN_RSA_MODULUS_SIZE = 2048; |
| /** @throws GeneralSecurityException if {@code typeUrl} is in invalid format. */ |
| public static void validateTypeUrl(String typeUrl) throws GeneralSecurityException { |
| if (!typeUrl.startsWith(TYPE_URL_PREFIX)) { |
| throw new GeneralSecurityException( |
| String.format( |
| "Error: type URL %s is invalid; it must start with %s.\n", typeUrl, TYPE_URL_PREFIX)); |
| } |
| if (typeUrl.length() == TYPE_URL_PREFIX.length()) { |
| throw new GeneralSecurityException( |
| String.format("Error: type URL %s is invalid; it has no message name.\n", typeUrl)); |
| } |
| } |
| |
| /** @throws InvalidAlgorithmParameterException if {@code sizeInBytes} is not supported. */ |
| public static void validateAesKeySize(int sizeInBytes) throws InvalidAlgorithmParameterException { |
| if (sizeInBytes != 16 && sizeInBytes != 32) { |
| throw new InvalidAlgorithmParameterException( |
| String.format( |
| "invalid key size %d; only 128-bit and 256-bit AES keys are supported", |
| sizeInBytes * 8)); |
| } |
| } |
| |
| /** |
| * @throws GeneralSecurityException if {@code candidate} is negative or larger than {@code |
| * maxExpected}. |
| */ |
| public static void validateVersion(int candidate, int maxExpected) |
| throws GeneralSecurityException { |
| if (candidate < 0 || candidate > maxExpected) { |
| throw new GeneralSecurityException( |
| String.format( |
| "key has version %d; only keys with version in range [0..%d] are supported", |
| candidate, maxExpected)); |
| } |
| } |
| |
| /** |
| * Validates whether {@code hash} is safe to use for digital signature. |
| * |
| * @throws GeneralSecurityException if {@code hash} is invalid or is not safe to use for digital |
| * signature. |
| */ |
| public static void validateSignatureHash(HashType hash) throws GeneralSecurityException { |
| switch (hash) { |
| case SHA256: // fall through |
| case SHA384: // fall through |
| case SHA512: |
| return; |
| default: |
| break; |
| } |
| throw new GeneralSecurityException("Unsupported hash: " + hash.name()); |
| } |
| |
| /** |
| * Validates whether {@code modulusSize} is at least 2048-bit. |
| * |
| * <p>To reach 128-bit security strength, RSA's modulus must be at least 3072-bit while 2048-bit |
| * RSA key only has 112-bit security. Nevertheless, a 2048-bit RSA key is considered safe by NIST |
| * until 2030 (see https://www.keylength.com/en/4/). |
| * |
| * @throws GeneralSecurityException if {@code modulusSize} is less than 2048-bit. |
| */ |
| public static void validateRsaModulusSize(int modulusSize) throws GeneralSecurityException { |
| if (modulusSize < MIN_RSA_MODULUS_SIZE) { |
| throw new GeneralSecurityException( |
| String.format( |
| "Modulus size is %d; only modulus size >= 2048-bit is supported", modulusSize)); |
| } |
| } |
| |
| /* |
| * @throws IOException if {@code f} exists. |
| */ |
| public static void validateNotExists(File f) throws IOException { |
| if (f.exists()) { |
| throw new IOException(String.format("%s exists, please choose another file\n", f)); |
| } |
| } |
| |
| /** @throws IOException if {@code f} does not exists. */ |
| public static void validateExists(File f) throws IOException { |
| if (!f.exists()) { |
| throw new IOException( |
| String.format("Error: %s doesn't exist, please choose another file\n", f)); |
| } |
| } |
| |
| /** |
| * Validates that {@code kmsKeyUri} starts with {@code expectedPrefix}, and removes the prefix. |
| * |
| * @throws IllegalArgumentException if {@code kmsKeyUri} is invalid. |
| */ |
| public static String validateKmsKeyUriAndRemovePrefix(String expectedPrefix, String kmsKeyUri) { |
| if (!kmsKeyUri.toLowerCase(Locale.US).startsWith(expectedPrefix)) { |
| throw new IllegalArgumentException( |
| String.format("key URI must start with %s", expectedPrefix)); |
| } |
| return kmsKeyUri.substring(expectedPrefix.length()); |
| } |
| |
| // See https://tools.ietf.org/html/rfc3986#section-2.3. |
| private static final String URI_UNRESERVED_CHARS = "([0-9a-zA-Z\\-\\.\\_~])+"; |
| |
| private static final Pattern GCP_KMS_CRYPTO_KEY_PATTERN = |
| Pattern.compile( |
| String.format( |
| "^projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s$", |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS), |
| Pattern.CASE_INSENSITIVE); |
| |
| private static final Pattern GCP_KMS_CRYPTO_KEY_VERSION_PATTERN = |
| Pattern.compile( |
| String.format( |
| "^projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%s$", |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS, |
| URI_UNRESERVED_CHARS), |
| Pattern.CASE_INSENSITIVE); |
| /** |
| * @throws GeneralSecurityException if {@code kmsKeyUri} is not a valid URI of a CryptoKey in |
| * Google Cloud KMS. |
| */ |
| public static void validateCryptoKeyUri(String kmsKeyUri) throws GeneralSecurityException { |
| if (!GCP_KMS_CRYPTO_KEY_PATTERN.matcher(kmsKeyUri).matches()) { |
| if (GCP_KMS_CRYPTO_KEY_VERSION_PATTERN.matcher(kmsKeyUri).matches()) { |
| throw new GeneralSecurityException( |
| "Invalid Google Cloud KMS Key URI. " |
| + "The URI must point to a CryptoKey, not a CryptoKeyVersion"); |
| } |
| throw new GeneralSecurityException( |
| "Invalid Google Cloud KMS Key URI. " |
| + "The URI must point to a CryptoKey in the format " |
| + "projects/*/locations/*/keyRings/*/cryptoKeys/*. " |
| + "See https://cloud.google.com/kms/docs/reference/rest/v1" |
| + "/projects.locations.keyRings.cryptoKeys#CryptoKey"); |
| } |
| } |
| } |