|
| 1 | +/* |
| 2 | + * Copyright (c) 2020-2025 Estonian Information System Authority |
| 3 | + * |
| 4 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | + * of this software and associated documentation files (the "Software"), to deal |
| 6 | + * in the Software without restriction, including without limitation the rights |
| 7 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 8 | + * copies of the Software, and to permit persons to whom the Software is |
| 9 | + * furnished to do so, subject to the following conditions: |
| 10 | + * |
| 11 | + * The above copyright notice and this permission notice shall be included in all |
| 12 | + * copies or substantial portions of the Software. |
| 13 | + * |
| 14 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 20 | + * SOFTWARE. |
| 21 | + */ |
| 22 | + |
1 | 23 | package eu.webeid.security.validator; |
2 | 24 |
|
3 | 25 | import eu.webeid.security.authtoken.SupportedSignatureAlgorithm; |
4 | 26 | import eu.webeid.security.authtoken.WebEidAuthToken; |
5 | 27 | import eu.webeid.security.certificate.CertificateLoader; |
6 | 28 | import eu.webeid.security.exceptions.AuthTokenException; |
7 | 29 | import eu.webeid.security.exceptions.AuthTokenParseException; |
| 30 | +import eu.webeid.security.exceptions.CertificateDecodingException; |
8 | 31 | import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch; |
9 | 32 | import eu.webeid.security.validator.ocsp.OcspClient; |
10 | 33 | import eu.webeid.security.validator.ocsp.OcspServiceProvider; |
@@ -64,52 +87,71 @@ public boolean supports(String format) { |
64 | 87 | public X509Certificate validate(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException { |
65 | 88 | final X509Certificate subjectCertificate = validateV1(token, currentChallengeNonce); |
66 | 89 |
|
| 90 | + validateFormat(token); |
| 91 | + final X509Certificate signingCertificate = validateSigningCertificateExists(token); |
| 92 | + validateSupportedSignatureAlgorithms(token.getSupportedSignatureAlgorithms()); |
| 93 | + validateSameSubject(subjectCertificate, signingCertificate); |
| 94 | + validateSameIssuer(subjectCertificate, signingCertificate); |
| 95 | + validateKeyUsage(signingCertificate); |
| 96 | + |
| 97 | + return subjectCertificate; |
| 98 | + } |
| 99 | + |
| 100 | + private static void validateSupportedSignatureAlgorithms(List<SupportedSignatureAlgorithm> algorithms) throws AuthTokenParseException { |
| 101 | + if (algorithms == null || algorithms.isEmpty()) { |
| 102 | + throw new AuthTokenParseException("'supportedSignatureAlgorithms' field is missing"); |
| 103 | + } |
| 104 | + |
| 105 | + boolean hasInvalid = algorithms.stream().anyMatch(supportedSignatureAlgorithm -> |
| 106 | + !SUPPORTED_CRYPTO_ALGORITHMS.contains(supportedSignatureAlgorithm.getCryptoAlgorithm()) || |
| 107 | + !SUPPORTED_HASH_FUNCTIONS.contains(supportedSignatureAlgorithm.getHashFunction()) || |
| 108 | + !SUPPORTED_PADDING_SCHEMES.contains(supportedSignatureAlgorithm.getPaddingScheme()) |
| 109 | + ); |
| 110 | + |
| 111 | + if (hasInvalid) { |
| 112 | + throw new AuthTokenParseException("Unsupported signature algorithm"); |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + private static void validateFormat(WebEidAuthToken token) throws AuthTokenParseException { |
67 | 117 | if (token.getFormat() == null || !token.getFormat().startsWith(SUPPORTED_PREFIX)) { |
68 | 118 | throw new AuthTokenParseException("Only token format '" + SUPPORTED_PREFIX + "' is supported by this validator"); |
69 | 119 | } |
| 120 | + } |
70 | 121 |
|
| 122 | + private static X509Certificate validateSigningCertificateExists(WebEidAuthToken token) throws AuthTokenParseException, CertificateDecodingException { |
71 | 123 | if (isNullOrEmpty(token.getUnverifiedSigningCertificate())) { |
72 | 124 | throw new AuthTokenParseException("'unverifiedSigningCertificate' field is missing, null or empty for format 'web-eid:1.1'"); |
73 | 125 | } |
| 126 | + return CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedSigningCertificate()); |
| 127 | + } |
74 | 128 |
|
75 | | - validateSupportedSignatureAlgorithms(token.getSupportedSignatureAlgorithms()); |
76 | | - |
77 | | - final X509Certificate signingCertificate = CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedSigningCertificate()); |
78 | | - |
79 | | - if (!subjectAndSigningCertificateSubjectsMatch(subjectCertificate.getSubjectX500Principal(), signingCertificate.getSubjectX500Principal())) { |
| 129 | + private static void validateSameSubject(X509Certificate subjectCertificate, X509Certificate signingCertificate) |
| 130 | + throws AuthTokenParseException { |
| 131 | + if (!subjectAndSigningCertificateSubjectsMatch( |
| 132 | + subjectCertificate.getSubjectX500Principal(), |
| 133 | + signingCertificate.getSubjectX500Principal())) { |
80 | 134 | throw new AuthTokenParseException("Signing certificate subject does not match authentication certificate subject"); |
81 | 135 | } |
| 136 | + } |
82 | 137 |
|
| 138 | + private static void validateSameIssuer(X509Certificate subjectCertificate, X509Certificate signingCertificate) |
| 139 | + throws AuthTokenParseException { |
83 | 140 | byte[] subjectCertificateAuthorityKeyIdentifier = getAuthorityKeyIdentifier(subjectCertificate); |
84 | 141 | byte[] signingCertificateAuthorityKeyIdentifier = getAuthorityKeyIdentifier(signingCertificate); |
85 | 142 |
|
86 | 143 | if (subjectCertificateAuthorityKeyIdentifier.length == 0 |
87 | 144 | || signingCertificateAuthorityKeyIdentifier.length == 0 |
88 | 145 | || !Arrays.equals(subjectCertificateAuthorityKeyIdentifier, signingCertificateAuthorityKeyIdentifier)) { |
89 | | - throw new AuthTokenParseException("Signing certificate is not issued by the same issuing authority as the authentication certificate"); |
| 146 | + throw new AuthTokenParseException( |
| 147 | + "Signing certificate is not issued by the same issuing authority as the authentication certificate"); |
90 | 148 | } |
| 149 | + } |
91 | 150 |
|
| 151 | + private static void validateKeyUsage(X509Certificate signingCertificate) throws AuthTokenParseException { |
92 | 152 | boolean[] keyUsage = signingCertificate.getKeyUsage(); |
93 | 153 | if (keyUsage == null || keyUsage.length <= KEY_USAGE_NON_REPUDIATION || !keyUsage[KEY_USAGE_NON_REPUDIATION]) { |
94 | | - throw new AuthTokenParseException("Signing certificate not suitable for signing"); |
95 | | - } |
96 | | - |
97 | | - return subjectCertificate; |
98 | | - } |
99 | | - |
100 | | - private static void validateSupportedSignatureAlgorithms(List<SupportedSignatureAlgorithm> algorithms) throws AuthTokenParseException { |
101 | | - if (algorithms == null || algorithms.isEmpty()) { |
102 | | - throw new AuthTokenParseException("'supportedSignatureAlgorithms' field is missing"); |
103 | | - } |
104 | | - |
105 | | - boolean hasInvalid = algorithms.stream().anyMatch(supportedSignatureAlgorithm -> |
106 | | - !SUPPORTED_CRYPTO_ALGORITHMS.contains(supportedSignatureAlgorithm.getCryptoAlgorithm()) || |
107 | | - !SUPPORTED_HASH_FUNCTIONS.contains(supportedSignatureAlgorithm.getHashFunction()) || |
108 | | - !SUPPORTED_PADDING_SCHEMES.contains(supportedSignatureAlgorithm.getPaddingScheme()) |
109 | | - ); |
110 | | - |
111 | | - if (hasInvalid) { |
112 | | - throw new AuthTokenParseException("Unsupported signature algorithm"); |
| 154 | + throw new AuthTokenParseException("Signing certificate key usage extension missing or does not contain non-repudiation bit required for digital signatures"); |
113 | 155 | } |
114 | 156 | } |
115 | 157 |
|
|
0 commit comments