1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.security.rsa;
  27 
  28 import java.io.*;
  29 import sun.security.util.*;
  30 import sun.security.x509.*;
  31 import java.security.AlgorithmParametersSpi;
  32 import java.security.NoSuchAlgorithmException;
  33 import java.security.spec.AlgorithmParameterSpec;
  34 import java.security.spec.InvalidParameterSpecException;
  35 import java.security.spec.MGF1ParameterSpec;
  36 import java.security.spec.PSSParameterSpec;
  37 import static java.security.spec.PSSParameterSpec.DEFAULT;
  38 
  39 /**
  40  * This class implements the PSS parameters used with the RSA
  41  * signatures in PSS padding. Here is its ASN.1 definition:
  42  * RSASSA-PSS-params ::= SEQUENCE {
  43  *   hashAlgorithm      [0] HashAlgorithm     DEFAULT sha1,
  44  *   maskGenAlgorithm   [1] MaskGenAlgorithm  DEFAULT mgf1SHA1,
  45  *   saltLength         [2] INTEGER           DEFAULT 20
  46  *   trailerField       [3] TrailerField      DEFAULT trailerFieldBC
  47  * }
  48  *
  49  * @author Valerie Peng
  50  *
  51  */
  52 
  53 public final class PSSParameters extends AlgorithmParametersSpi {
  54 
  55     private String mdName;
  56     private MGF1ParameterSpec mgfSpec;
  57     private int saltLength;
  58     private int trailerField;
  59 
  60     private static final ObjectIdentifier OID_MGF1 =
  61            ObjectIdentifier.newInternal(new int[] {1,2,840,113549,1,1,8});
  62 
  63     public PSSParameters() {
  64     }
  65 
  66     @Override
  67     protected void engineInit(AlgorithmParameterSpec paramSpec)
  68             throws InvalidParameterSpecException {
  69         if (!(paramSpec instanceof PSSParameterSpec)) {
  70             throw new InvalidParameterSpecException
  71                 ("Inappropriate parameter specification");
  72         }
  73         PSSParameterSpec spec = (PSSParameterSpec) paramSpec;
  74         this.mdName = spec.getDigestAlgorithm();
  75         String mgfName = spec.getMGFAlgorithm();
  76         if (!mgfName.equalsIgnoreCase("MGF1")) {
  77             throw new InvalidParameterSpecException("Unsupported mgf " +
  78                 mgfName + "; MGF1 only");
  79         }
  80         AlgorithmParameterSpec mgfSpec = spec.getMGFParameters();
  81         if (!(mgfSpec instanceof MGF1ParameterSpec)) {
  82             throw new InvalidParameterSpecException("Inappropriate mgf " +
  83                 "parameters; non-null MGF1ParameterSpec only");
  84         }
  85         this.mgfSpec = (MGF1ParameterSpec) mgfSpec;
  86         this.saltLength = spec.getSaltLength();
  87         this.trailerField = spec.getTrailerField();
  88     }
  89 
  90     @Override
  91     protected void engineInit(byte[] encoded) throws IOException {
  92         // first initialize with the DEFAULT values before
  93         // retrieving from the encoding bytes
  94         this.mdName = DEFAULT.getDigestAlgorithm();
  95         this.mgfSpec = (MGF1ParameterSpec) DEFAULT.getMGFParameters();
  96         this.saltLength = DEFAULT.getSaltLength();
  97         this.trailerField = DEFAULT.getTrailerField();
  98 
  99         DerInputStream der = new DerInputStream(encoded);
 100         DerValue[] datum = der.getSequence(4);
 101         for (DerValue d : datum) {
 102             if (d.isContextSpecific((byte) 0x00)) {
 103                 // hash algid
 104                 this.mdName = AlgorithmId.parse
 105                     (d.data.getDerValue()).getName();
 106             } else if (d.isContextSpecific((byte) 0x01)) {
 107                 // mgf algid
 108                 AlgorithmId val = AlgorithmId.parse(d.data.getDerValue());
 109                 if (!val.getOID().equals(OID_MGF1)) {
 110                     throw new IOException("Only MGF1 mgf is supported");
 111                 }
 112                 AlgorithmId params = AlgorithmId.parse(
 113                     new DerValue(val.getEncodedParams()));
 114                 String mgfDigestName = params.getName();
 115                 switch (mgfDigestName) {
 116                 case "SHA-1":
 117                     this.mgfSpec = MGF1ParameterSpec.SHA1;
 118                     break;
 119                 case "SHA-224":
 120                     this.mgfSpec = MGF1ParameterSpec.SHA224;
 121                     break;
 122                 case "SHA-256":
 123                     this.mgfSpec = MGF1ParameterSpec.SHA256;
 124                     break;
 125                 case "SHA-384":
 126                     this.mgfSpec = MGF1ParameterSpec.SHA384;
 127                     break;
 128                 case "SHA-512":
 129                     this.mgfSpec = MGF1ParameterSpec.SHA512;
 130                     break;
 131                 case "SHA-512/224":
 132                     this.mgfSpec = MGF1ParameterSpec.SHA512_224;
 133                     break;
 134                 case "SHA-512/256":
 135                     this.mgfSpec = MGF1ParameterSpec.SHA512_256;
 136                     break;
 137                 default:
 138                     throw new IOException
 139                         ("Unrecognized message digest algorithm " +
 140                         mgfDigestName);
 141                 }
 142             } else if (d.isContextSpecific((byte) 0x02)) {
 143                 // salt length
 144                 this.saltLength = d.data.getDerValue().getInteger();
 145                 if (this.saltLength < 0) {
 146                     throw new IOException("Negative value for saltLength");
 147                 }
 148             } else if (d.isContextSpecific((byte) 0x03)) {
 149                 // trailer field
 150                 this.trailerField = d.data.getDerValue().getInteger();
 151                 if (this.trailerField != 1) {
 152                     throw new IOException("Unsupported trailerField value " +
 153                     this.trailerField);
 154                 }
 155             } else {
 156                 throw new IOException("Invalid encoded PSSParameters");
 157             }
 158         }
 159     }
 160 
 161     @Override
 162     protected void engineInit(byte[] encoded, String decodingMethod)
 163             throws IOException {
 164         if ((decodingMethod != null) &&
 165             (!decodingMethod.equalsIgnoreCase("ASN.1"))) {
 166             throw new IllegalArgumentException("Only support ASN.1 format");
 167         }
 168         engineInit(encoded);
 169     }
 170 
 171     @Override
 172     protected <T extends AlgorithmParameterSpec>
 173             T engineGetParameterSpec(Class<T> paramSpec)
 174             throws InvalidParameterSpecException {
 175         if (PSSParameterSpec.class.isAssignableFrom(paramSpec)) {
 176             return paramSpec.cast(
 177                 new PSSParameterSpec(mdName, "MGF1", mgfSpec,
 178                                      saltLength, trailerField));
 179         } else {
 180             throw new InvalidParameterSpecException
 181                 ("Inappropriate parameter specification");
 182         }
 183     }
 184 
 185     @Override
 186     protected byte[] engineGetEncoded() throws IOException {
 187         DerOutputStream tmp = new DerOutputStream();
 188         DerOutputStream tmp2, tmp3;
 189 
 190         // MD
 191         AlgorithmId mdAlgId;
 192         try {
 193             mdAlgId = AlgorithmId.get(mdName);
 194         } catch (NoSuchAlgorithmException nsae) {
 195             throw new IOException("AlgorithmId " + mdName +
 196                                   " impl not found");
 197         }
 198         tmp2 = new DerOutputStream();
 199         mdAlgId.derEncode(tmp2);
 200         tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)0),
 201                       tmp2);
 202 
 203         // MGF
 204         tmp2 = new DerOutputStream();
 205         tmp2.putOID(OID_MGF1);
 206         AlgorithmId mgfDigestId;
 207         try {
 208             mgfDigestId = AlgorithmId.get(mgfSpec.getDigestAlgorithm());
 209         } catch (NoSuchAlgorithmException nase) {
 210             throw new IOException("AlgorithmId " +
 211                     mgfSpec.getDigestAlgorithm() + " impl not found");
 212         }
 213         mgfDigestId.encode(tmp2);
 214         tmp3 = new DerOutputStream();
 215         tmp3.write(DerValue.tag_Sequence, tmp2);
 216         tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)1),
 217                   tmp3);
 218 
 219         // SaltLength
 220         tmp2 = new DerOutputStream();
 221         tmp2.putInteger(saltLength);
 222         tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)2),
 223                   tmp2);
 224 
 225         // TrailerField
 226         tmp2 = new DerOutputStream();
 227         tmp2.putInteger(trailerField);
 228         tmp.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte)3),
 229                   tmp2);
 230 
 231         // Put all together under a SEQUENCE tag
 232         DerOutputStream out = new DerOutputStream();
 233         out.write(DerValue.tag_Sequence, tmp);
 234         return out.toByteArray();
 235     }
 236 
 237     @Override
 238     protected byte[] engineGetEncoded(String encMethod) throws IOException {
 239         if ((encMethod != null) &&
 240             (!encMethod.equalsIgnoreCase("ASN.1"))) {
 241             throw new IllegalArgumentException("Only support ASN.1 format");
 242         }
 243         return engineGetEncoded();
 244     }
 245 
 246     @Override
 247     protected String engineToString() {
 248         StringBuilder sb = new StringBuilder();
 249         sb.append("MD: " + mdName + "\n")
 250             .append("MGF: MGF1" + mgfSpec.getDigestAlgorithm() + "\n")
 251             .append("SaltLength: " + saltLength + "\n")
 252             .append("TrailerField: " + trailerField + "\n");
 253         return sb.toString();
 254     }
 255 }