1 // Copyright 2023 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 //////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.signature; 18 19 import com.google.errorprone.annotations.CanIgnoreReturnValue; 20 import com.google.errorprone.annotations.Immutable; 21 import java.math.BigInteger; 22 import java.security.GeneralSecurityException; 23 import java.security.InvalidAlgorithmParameterException; 24 import java.util.Objects; 25 import javax.annotation.Nullable; 26 27 /** 28 * Describes the parameters of a {@link RsaSsaPssPublicKey} and {@link RsaSsaPssPrivateKey}. 29 * 30 * <p>Standard: https://datatracker.ietf.org/doc/html/rfc8017#section-8.1 31 */ 32 public final class RsaSsaPssParameters extends SignatureParameters { 33 /** 34 * Describes details of the signature. 35 * 36 * <p>The usual key is used for variant "NO_PREFIX". Other variants slightly change how the 37 * signature is computed, or add a prefix to every computation depending on the key id. 38 */ 39 @Immutable 40 public static final class Variant { 41 public static final Variant TINK = new Variant("TINK"); 42 public static final Variant CRUNCHY = new Variant("CRUNCHY"); 43 public static final Variant LEGACY = new Variant("LEGACY"); 44 public static final Variant NO_PREFIX = new Variant("NO_PREFIX"); 45 46 private final String name; 47 Variant(String name)48 private Variant(String name) { 49 this.name = name; 50 } 51 52 @Override toString()53 public String toString() { 54 return name; 55 } 56 } 57 58 /** The Hash algorithm used. */ 59 @Immutable 60 public static final class HashType { 61 public static final HashType SHA256 = new HashType("SHA256"); 62 public static final HashType SHA384 = new HashType("SHA384"); 63 public static final HashType SHA512 = new HashType("SHA512"); 64 65 private final String name; 66 HashType(String name)67 private HashType(String name) { 68 this.name = name; 69 } 70 71 @Override toString()72 public String toString() { 73 return name; 74 } 75 } 76 77 public static final BigInteger F4 = BigInteger.valueOf(65537); 78 79 /** Builds a new RsaSsaPssParameters instance. */ 80 public static final class Builder { 81 @Nullable private Integer modulusSizeBits = null; 82 @Nullable private BigInteger publicExponent = F4; 83 @Nullable private HashType sigHashType = null; 84 @Nullable private HashType mgf1HashType = null; 85 @Nullable private Integer saltLengthBytes = null; 86 private Variant variant = Variant.NO_PREFIX; 87 Builder()88 private Builder() {} 89 90 @CanIgnoreReturnValue setModulusSizeBits(int modulusSizeBits)91 public Builder setModulusSizeBits(int modulusSizeBits) { 92 this.modulusSizeBits = modulusSizeBits; 93 return this; 94 } 95 96 @CanIgnoreReturnValue setPublicExponent(BigInteger e)97 public Builder setPublicExponent(BigInteger e) { 98 this.publicExponent = e; 99 return this; 100 } 101 102 @CanIgnoreReturnValue setVariant(Variant variant)103 public Builder setVariant(Variant variant) { 104 this.variant = variant; 105 return this; 106 } 107 108 @CanIgnoreReturnValue setSigHashType(HashType sigHashType)109 public Builder setSigHashType(HashType sigHashType) { 110 this.sigHashType = sigHashType; 111 return this; 112 } 113 114 @CanIgnoreReturnValue setMgf1HashType(HashType mgf1HashType)115 public Builder setMgf1HashType(HashType mgf1HashType) { 116 this.mgf1HashType = mgf1HashType; 117 return this; 118 } 119 120 @CanIgnoreReturnValue setSaltLengthBytes(int saltLengthBytes)121 public Builder setSaltLengthBytes(int saltLengthBytes) throws GeneralSecurityException { 122 if (saltLengthBytes < 0) { 123 throw new GeneralSecurityException( 124 String.format( 125 "Invalid salt length in bytes %d; salt length must be positive", saltLengthBytes)); 126 } 127 this.saltLengthBytes = saltLengthBytes; 128 return this; 129 } 130 131 private static final BigInteger TWO = BigInteger.valueOf(2); 132 private static final BigInteger PUBLIC_EXPONENT_UPPER_BOUND = TWO.pow(256); 133 private static final int MIN_RSA_MODULUS_SIZE = 2048; 134 validatePublicExponent(BigInteger publicExponent)135 private void validatePublicExponent(BigInteger publicExponent) 136 throws InvalidAlgorithmParameterException { 137 // We use the validation of the public exponent as defined in 138 // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf, B.3 139 int c = publicExponent.compareTo(F4); 140 if (c == 0) { 141 // publicExponent is F4. 142 return; 143 } 144 if (c < 0) { 145 // publicExponent is smaller than F4. 146 throw new InvalidAlgorithmParameterException("Public exponent must be at least 65537."); 147 } 148 if (publicExponent.mod(TWO).equals(BigInteger.ZERO)) { 149 // publicExponent is even. This is invalid since it is not co-prime to p-1. 150 throw new InvalidAlgorithmParameterException("Invalid public exponent"); 151 } 152 if (publicExponent.compareTo(PUBLIC_EXPONENT_UPPER_BOUND) > 0) { 153 // publicExponent is larger than PUBLIC_EXPONENT_UPPER_BOUND. 154 throw new InvalidAlgorithmParameterException( 155 "Public exponent cannot be larger than 2^256."); 156 } 157 } 158 build()159 public RsaSsaPssParameters build() throws GeneralSecurityException { 160 if (modulusSizeBits == null) { 161 throw new GeneralSecurityException("key size is not set"); 162 } 163 if (publicExponent == null) { 164 throw new GeneralSecurityException("publicExponent is not set"); 165 } 166 if (sigHashType == null) { 167 throw new GeneralSecurityException("signature hash type is not set"); 168 } 169 if (mgf1HashType == null) { 170 throw new GeneralSecurityException("mgf1 hash type is not set"); 171 } 172 if (variant == null) { 173 throw new GeneralSecurityException("variant is not set"); 174 } 175 if (saltLengthBytes == null) { 176 throw new GeneralSecurityException("salt length is not set"); 177 } 178 if (modulusSizeBits < MIN_RSA_MODULUS_SIZE) { 179 throw new InvalidAlgorithmParameterException( 180 String.format( 181 "Invalid key size in bytes %d; must be at least %d bits", 182 modulusSizeBits, MIN_RSA_MODULUS_SIZE)); 183 } 184 if (sigHashType != mgf1HashType) { 185 throw new GeneralSecurityException("MGF1 hash is different from signature hash"); 186 } 187 validatePublicExponent(publicExponent); 188 return new RsaSsaPssParameters( 189 modulusSizeBits, publicExponent, variant, sigHashType, mgf1HashType, saltLengthBytes); 190 } 191 } 192 193 private final int modulusSizeBits; 194 private final BigInteger publicExponent; 195 private final Variant variant; 196 private final HashType sigHashType; 197 private final HashType mgf1HashType; 198 private final int saltLengthBytes; 199 RsaSsaPssParameters( int modulusSizeBits, BigInteger publicExponent, Variant variant, HashType sigHashType, HashType mgf1HashType, int saltLengthBytes)200 private RsaSsaPssParameters( 201 int modulusSizeBits, 202 BigInteger publicExponent, 203 Variant variant, 204 HashType sigHashType, 205 HashType mgf1HashType, 206 int saltLengthBytes) { 207 this.modulusSizeBits = modulusSizeBits; 208 this.publicExponent = publicExponent; 209 this.variant = variant; 210 this.sigHashType = sigHashType; 211 this.mgf1HashType = mgf1HashType; 212 this.saltLengthBytes = saltLengthBytes; 213 } 214 builder()215 public static Builder builder() { 216 return new Builder(); 217 } 218 getModulusSizeBits()219 public int getModulusSizeBits() { 220 return modulusSizeBits; 221 } 222 getPublicExponent()223 public BigInteger getPublicExponent() { 224 return publicExponent; 225 } 226 getVariant()227 public Variant getVariant() { 228 return variant; 229 } 230 getSigHashType()231 public HashType getSigHashType() { 232 return sigHashType; 233 } 234 getMgf1HashType()235 public HashType getMgf1HashType() { 236 return mgf1HashType; 237 } 238 getSaltLengthBytes()239 public int getSaltLengthBytes() { 240 return saltLengthBytes; 241 } 242 243 @Override equals(Object o)244 public boolean equals(Object o) { 245 if (!(o instanceof RsaSsaPssParameters)) { 246 return false; 247 } 248 RsaSsaPssParameters that = (RsaSsaPssParameters) o; 249 return that.getModulusSizeBits() == getModulusSizeBits() 250 && Objects.equals(that.getPublicExponent(), getPublicExponent()) 251 && Objects.equals(that.getVariant(), getVariant()) 252 && Objects.equals(that.getSigHashType(), getSigHashType()) 253 && Objects.equals(that.getMgf1HashType(), getMgf1HashType()) 254 && that.getSaltLengthBytes() == getSaltLengthBytes(); 255 } 256 257 @Override hashCode()258 public int hashCode() { 259 return Objects.hash( 260 RsaSsaPssParameters.class, 261 modulusSizeBits, 262 publicExponent, 263 variant, 264 sigHashType, 265 mgf1HashType, 266 saltLengthBytes); 267 } 268 269 @Override hasIdRequirement()270 public boolean hasIdRequirement() { 271 return variant != Variant.NO_PREFIX; 272 } 273 274 @Override toString()275 public String toString() { 276 return "RSA SSA PSS Parameters (variant: " 277 + variant 278 + ", signature hashType: " 279 + sigHashType 280 + ", mgf1 hashType: " 281 + mgf1HashType 282 + ", saltLengthBytes: " 283 + saltLengthBytes 284 + ", publicExponent: " 285 + publicExponent 286 + ", and " 287 + modulusSizeBits 288 + "-bit modulus)"; 289 } 290 } 291