View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.wss4j.common.crypto;
21  
22  import java.security.PublicKey;
23  import java.security.cert.X509Certificate;
24  import java.security.interfaces.*;
25  import java.util.Set;
26  
27  import javax.xml.crypto.dsig.Reference;
28  import javax.xml.crypto.dsig.Transform;
29  import javax.xml.crypto.dsig.XMLSignature;
30  
31  import org.apache.wss4j.common.ext.WSSecurityException;
32  import org.apache.xml.security.exceptions.DERDecodingException;
33  import org.apache.xml.security.utils.DERDecoderUtils;
34  import org.apache.xml.security.utils.KeyUtils;
35  
36  /**
37   * Validate signature/encryption/etc. algorithms against an AlgorithmSuite policy.
38   */
39  public class AlgorithmSuiteValidator {
40  
41      private static final org.slf4j.Logger LOG =
42          org.slf4j.LoggerFactory.getLogger(AlgorithmSuiteValidator.class);
43  
44      private final AlgorithmSuite algorithmSuite;
45  
46      public AlgorithmSuiteValidator(
47          AlgorithmSuite algorithmSuite
48      ) {
49          this.algorithmSuite = algorithmSuite;
50      }
51  
52      /**
53       * Check the Signature Method
54       */
55      public void checkSignatureMethod(
56          String signatureMethod
57      ) throws WSSecurityException {
58          Set<String> allowedSignatureMethods = algorithmSuite.getSignatureMethods();
59          if (!allowedSignatureMethods.isEmpty()
60              && !allowedSignatureMethods.contains(signatureMethod)) {
61              LOG.warn(
62                  "SignatureMethod " + signatureMethod + " does not match required values"
63              );
64              throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
65          }
66      }
67  
68      /**
69       * Check the C14n Algorithm
70       */
71      public void checkC14nAlgorithm(
72          String c14nAlgorithm
73      ) throws WSSecurityException {
74          Set<String> allowedC14nAlgorithms = algorithmSuite.getC14nAlgorithms();
75          if (!allowedC14nAlgorithms.isEmpty() && !allowedC14nAlgorithms.contains(c14nAlgorithm)) {
76              LOG.warn(
77                  "C14nMethod " + c14nAlgorithm + " does not match required value"
78              );
79              throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
80          }
81      }
82  
83      /**
84       * Check the Signature Algorithms
85       */
86      public void checkSignatureAlgorithms(
87          XMLSignature xmlSignature
88      ) throws WSSecurityException {
89          // Signature Algorithm
90          String signatureMethod =
91              xmlSignature.getSignedInfo().getSignatureMethod().getAlgorithm();
92          checkSignatureMethod(signatureMethod);
93  
94          // C14n Algorithm
95          String c14nMethod =
96              xmlSignature.getSignedInfo().getCanonicalizationMethod().getAlgorithm();
97          checkC14nAlgorithm(c14nMethod);
98  
99          for (Object refObject : xmlSignature.getSignedInfo().getReferences()) {
100             Reference reference = (Reference)refObject;
101             // Digest Algorithm
102             String digestMethod = reference.getDigestMethod().getAlgorithm();
103             Set<String> allowedDigestAlgorithms = algorithmSuite.getDigestAlgorithms();
104             if (!allowedDigestAlgorithms.isEmpty()
105                     && !allowedDigestAlgorithms.contains(digestMethod)) {
106                 LOG.warn(
107                     "DigestMethod " + digestMethod + " does not match required value"
108                 );
109                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
110             }
111 
112             // Transform Algorithms
113             for (int i = 0; i < reference.getTransforms().size(); i++) {
114                 Transform transform = (Transform)reference.getTransforms().get(i);
115                 String algorithm = transform.getAlgorithm();
116                 Set<String> allowedTransformAlgorithms =
117                         algorithmSuite.getTransformAlgorithms();
118                 if (!allowedTransformAlgorithms.isEmpty()
119                         && !allowedTransformAlgorithms.contains(algorithm)) {
120                     LOG.warn(
121                         "Transform method " + algorithm + " does not match required value"
122                     );
123                     throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
124                 }
125             }
126         }
127     }
128 
129     public void checkEncryptionKeyWrapAlgorithm(
130         String keyWrapAlgorithm
131     ) throws WSSecurityException {
132         Set<String> keyWrapAlgorithms = algorithmSuite.getKeyWrapAlgorithms();
133         if (!keyWrapAlgorithms.isEmpty()
134             && !keyWrapAlgorithms.contains(keyWrapAlgorithm)) {
135             LOG.warn(
136                 "The Key transport method does not match the requirement"
137             );
138             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
139         }
140     }
141 
142     public void checkKeyAgreementMethodAlgorithm(
143             String keyAgreementMethodAlgorithm
144     ) throws WSSecurityException {
145         Set<String> keyAgreementMethodAlgorithms = algorithmSuite.getKeyAgreementMethodAlgorithms();
146         if (!keyAgreementMethodAlgorithms.isEmpty()
147                 && !keyAgreementMethodAlgorithms.contains(keyAgreementMethodAlgorithm)) {
148             LOG.warn(
149                     "The Key agreement method does not match the requirement"
150             );
151             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
152         }
153     }
154 
155     /**
156      * Method to check the Key Derivation algorithm is on the approved list  of the
157      * AlgorithmSuite configuration.
158      * @param keyDerivationFunction the key derivation function to be validated
159      * @throws WSSecurityException if the approved list is not empty and the key
160      * derivation function is not on the list
161      */
162     public void checkKeyDerivationFunction(
163             String keyDerivationFunction
164     ) throws WSSecurityException {
165         Set<String> keyDerivationFunctions = algorithmSuite.getDerivedKeyAlgorithms();
166         if (!keyDerivationFunctions.isEmpty()
167                 && !keyDerivationFunctions.contains(keyDerivationFunction)) {
168             LOG.warn(
169                     "The Key derivation function does not match the requirement"
170             );
171             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
172         }
173     }
174 
175     public void checkSymmetricEncryptionAlgorithm(
176         String symmetricAlgorithm
177     ) throws WSSecurityException {
178         Set<String> encryptionMethods = algorithmSuite.getEncryptionMethods();
179         if (!encryptionMethods.isEmpty()
180             && !encryptionMethods.contains(symmetricAlgorithm)) {
181             LOG.warn(
182                 "The encryption algorithm does not match the requirement"
183             );
184             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
185         }
186     }
187 
188     /**
189      * Check the asymmetric key length
190      */
191     public void checkAsymmetricKeyLength(
192         X509Certificate[] x509Certificates
193     ) throws WSSecurityException {
194         if (x509Certificates == null) {
195             return;
196         }
197 
198         for (X509Certificate cert : x509Certificates) {
199             checkAsymmetricKeyLength(cert.getPublicKey());
200         }
201     }
202 
203     /**
204      * Check the asymmetric key length
205      */
206     public void checkAsymmetricKeyLength(
207         X509Certificate x509Certificate
208     ) throws WSSecurityException {
209         if (x509Certificate == null) {
210             return;
211         }
212 
213         checkAsymmetricKeyLength(x509Certificate.getPublicKey());
214     }
215 
216     /**
217      * Check the asymmetric key length
218      */
219     public void checkAsymmetricKeyLength(
220         PublicKey publicKey
221     ) throws WSSecurityException {
222         if (publicKey == null) {
223             return;
224         }
225         if (publicKey instanceof RSAPublicKey) {
226             int modulus = ((RSAPublicKey)publicKey).getModulus().bitLength();
227             if (modulus < algorithmSuite.getMinimumAsymmetricKeyLength()
228                 || modulus > algorithmSuite.getMaximumAsymmetricKeyLength()) {
229                 LOG.warn(
230                     "The asymmetric key length does not match the requirement"
231                 );
232                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
233             }
234         } else if (publicKey instanceof DSAPublicKey) {
235             int length = ((DSAPublicKey)publicKey).getParams().getP().bitLength();
236             if (length < algorithmSuite.getMinimumAsymmetricKeyLength()
237                 || length > algorithmSuite.getMaximumAsymmetricKeyLength()) {
238                 LOG.warn(
239                     "The asymmetric key length does not match the requirement"
240                 );
241                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
242             }
243         } else if (publicKey instanceof ECPublicKey) {
244             final ECPublicKey ecpriv = (ECPublicKey) publicKey;
245             final java.security.spec.ECParameterSpec spec = ecpriv.getParams();
246             int length = spec.getOrder().bitLength();
247             if (length < algorithmSuite.getMinimumEllipticCurveKeyLength()
248                     || length > algorithmSuite.getMaximumEllipticCurveKeyLength()) {
249                 LOG.warn("The elliptic curve key length does not match the requirement");
250                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
251             }
252         } else {
253             // Try with last supported key types EdEC and XDH
254             int keySize = getEdECndXDHKeyLength(publicKey);
255             if (keySize < algorithmSuite.getMinimumEllipticCurveKeyLength()
256                     || keySize > algorithmSuite.getMaximumEllipticCurveKeyLength()) {
257                 LOG.warn(
258                         "The asymmetric key length does not match the requirement"
259                 );
260                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
261             }
262         }
263     }
264 
265     /**
266      * A generic method to determinate key length for keys x25519, x448, ed25519 and ed448 keys. Method does not rely on
267      * any specific implementation of the key, but uses OID to determine the key type.
268      *
269      * @param publicKey the public key to check the key length
270      * @return the key length in bits
271      * @throws WSSecurityException if the key is not  EdEC or XDH or if length can not be determined
272      */
273     private int getEdECndXDHKeyLength(PublicKey publicKey) throws  WSSecurityException {
274         String keyAlgorithmOId;
275         try {
276             keyAlgorithmOId = DERDecoderUtils.getAlgorithmIdFromPublicKey(publicKey);
277         } catch (DERDecodingException e) {
278             LOG.warn("Can not parse the public key to determine key size!", e);
279             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
280         }
281         KeyUtils.KeyType keyType = KeyUtils.KeyType.getByOid(keyAlgorithmOId);
282         if (keyType == null) {
283             LOG.warn("An unknown public key was provided");
284             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
285         }
286 
287         return switch (keyType) {
288             case ED25519, X25519 -> 256;
289             case ED448, X448 -> 456;
290             default -> {
291                 LOG.warn(
292                         "An unknown public key was provided"
293                 );
294                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
295             }
296         };
297     }
298 
299     /**
300      * Check the symmetric key length
301      */
302     public void checkSymmetricKeyLength(
303         int secretKeyLength
304     ) throws WSSecurityException {
305         if (secretKeyLength < (algorithmSuite.getMinimumSymmetricKeyLength() / 8)
306             || secretKeyLength > (algorithmSuite.getMaximumSymmetricKeyLength() / 8)) {
307             LOG.warn(
308                 "The symmetric key length does not match the requirement"
309             );
310             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
311         }
312     }
313 
314     /**
315      * Check Signature Derived Key length (in bytes)
316      */
317     public void checkSignatureDerivedKeyLength(
318         int derivedKeyLength
319     ) throws WSSecurityException {
320         int requiredKeyLength = algorithmSuite.getSignatureDerivedKeyLength();
321         if (requiredKeyLength > 0 && (derivedKeyLength / 8) != requiredKeyLength) {
322             LOG.warn(
323                 "The signature derived key length of " + derivedKeyLength + " does not match"
324                 + " the requirement of " + requiredKeyLength
325             );
326         }
327     }
328 
329     /**
330      * Check Encryption Derived Key length (in bytes)
331      */
332     public void checkEncryptionDerivedKeyLength(
333         int derivedKeyLength
334     ) throws WSSecurityException {
335         int requiredKeyLength = algorithmSuite.getEncryptionDerivedKeyLength();
336         if (requiredKeyLength > 0 && (derivedKeyLength / 8) != requiredKeyLength) {
337             LOG.warn(
338                 "The encryption derived key length of " + derivedKeyLength + " does not match"
339                 + " the requirement of " + requiredKeyLength
340             );
341         }
342     }
343 
344     /**
345      * Check Derived Key algorithm
346      */
347     public void checkDerivedKeyAlgorithm(
348         String algorithm
349     ) throws WSSecurityException {
350         Set<String> derivedKeyAlgorithms = algorithmSuite.getDerivedKeyAlgorithms();
351         if (!derivedKeyAlgorithms.isEmpty()
352             && !derivedKeyAlgorithms.contains(algorithm)) {
353             LOG.warn(
354                 "The Derived Key Algorithm does not match the requirement"
355             );
356             throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY);
357         }
358     }
359 
360 }