1 | /** | |
2 | Copyright 2017 Carlos Macasaet | |
3 | ||
4 | Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | you may not use this file except in compliance with the License. | |
6 | You may obtain a copy of the License at | |
7 | ||
8 | http://www.apache.org/licenses/LICENSE-2.0 | |
9 | ||
10 | Unless required by applicable law or agreed to in writing, software | |
11 | distributed under the License is distributed on an "AS IS" BASIS, | |
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | See the License for the specific language governing permissions and | |
14 | limitations under the License. | |
15 | */ | |
16 | package com.macasaet.fernet; | |
17 | ||
18 | import java.time.Clock; | |
19 | import java.time.Duration; | |
20 | import java.time.Instant; | |
21 | import java.time.ZoneOffset; | |
22 | import java.time.temporal.TemporalAmount; | |
23 | import java.util.Collection; | |
24 | import java.util.concurrent.ForkJoinPool; | |
25 | import java.util.function.Function; | |
26 | import java.util.function.Predicate; | |
27 | ||
28 | /** | |
29 | * This class validates a token according to the Fernet specification. It may be extended to provide domain-specific | |
30 | * validation of the decrypted content of the token. If you use a dependency injection / inversion of control framework, | |
31 | * it would be appropriate for a subclass to be a singleton which accesses a data store. | |
32 | * | |
33 | * <p>Copyright © 2017 Carlos Macasaet.</p> | |
34 | * | |
35 | * @param <T> | |
36 | * The type of the payload. The Fernet token encodes the payload in binary. The type T should be a domain | |
37 | * object or data transfer object representation of that data. | |
38 | * @see StringObjectValidator | |
39 | * @see StringValidator | |
40 | * @author Carlos Macasaet | |
41 | */ | |
42 | public interface Validator<T> { | |
43 | ||
44 | /** | |
45 | * Override this method if your application uses a custom clock. The default implementation returns a clock in the | |
46 | * UTC time zone with second granularity. | |
47 | * | |
48 | * @return The Clock used for all validation operations. | |
49 | */ | |
50 | default Clock getClock() { | |
51 |
1
1. getClock : mutated return of Object value for com/macasaet/fernet/Validator::getClock to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return Clock.tickSeconds(ZoneOffset.UTC); |
52 | } | |
53 | ||
54 | /** | |
55 | * Override this method to define the maximum allowable age of a token. Note that the time-to-live (TTL) check is | |
56 | * done before applying business rules. So if the {@link Predicate} defined by {@link #getObjectValidator()} applies | |
57 | * varying TTL checks depending on the payload (e.g. progressively shorter TTLs), then the TTL specified here must | |
58 | * be at least as long as any defined in the Predicate. | |
59 | * | |
60 | * @return the maximum allowable age of a token | |
61 | */ | |
62 | default TemporalAmount getTimeToLive() { | |
63 |
1
1. getTimeToLive : mutated return of Object value for com/macasaet/fernet/Validator::getTimeToLive to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return Duration.ofSeconds(60); |
64 | } | |
65 | ||
66 | /** | |
67 | * Override this method to define a custom acceptable clock skew. Fernet tokens with a timestamp that is too far in | |
68 | * the future will be considered invalid. This essentially defines how much tolerance your application has for clock | |
69 | * skew between VMs in the system. The default value is 60 seconds. | |
70 | * | |
71 | * @return the tolerance for clock skew between VMs. | |
72 | */ | |
73 | default TemporalAmount getMaxClockSkew() { | |
74 |
1
1. getMaxClockSkew : mutated return of Object value for com/macasaet/fernet/Validator::getMaxClockSkew to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return Duration.ofSeconds(60); |
75 | } | |
76 | ||
77 | /** | |
78 | * Implement this to define application-specific security rules. By default, no additional validation is performed. | |
79 | * | |
80 | * @return a method that implements custom validation logic on the deserialised payload | |
81 | */ | |
82 | default Predicate<T> getObjectValidator() { | |
83 |
2
1. getObjectValidator : mutated return of Object value for com/macasaet/fernet/Validator::getObjectValidator to ( if (x != null) null else throw new RuntimeException ) → KILLED 2. lambda$getObjectValidator$0 : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return payload -> true; |
84 | } | |
85 | ||
86 | /** | |
87 | * Implement this to define how decrypted content is deserialised into domain objects. | |
88 | * | |
89 | * @return a method for converting the decrypted payload into a domain object | |
90 | */ | |
91 | Function<byte[], T> getTransformer(); | |
92 | ||
93 | /** | |
94 | * Check the validity of the token then decrypt and deserialise the payload. | |
95 | * | |
96 | * @param key the stored shared secret key | |
97 | * @param token the client-provided token of unknown validity | |
98 | * @return the deserialised contents of the token | |
99 | * @throws TokenValidationException if the token is invalid. | |
100 | */ | |
101 | @SuppressWarnings({"PMD.LawOfDemeter", "PMD.DataflowAnomalyAnalysis"}) | |
102 | default T validateAndDecrypt(final Key key, final Token token) { | |
103 | final Instant now = Instant.now(getClock()); | |
104 | final byte[] plainText = token.validateAndDecrypt(key, now.minus(getTimeToLive()), now.plus(getMaxClockSkew())); | |
105 | final T object = getTransformer().apply(plainText); | |
106 |
1
1. validateAndDecrypt : negated conditional → KILLED |
if (!getObjectValidator().test(object)) { |
107 |
3
1. validateAndDecrypt : changed conditional boundary → SURVIVED 2. validateAndDecrypt : Changed increment from -1 to 1 → KILLED 3. validateAndDecrypt : negated conditional → KILLED |
for (int i = plainText.length; --i >= 0; plainText[i] = 0); |
108 | throw new PayloadValidationException("Invalid Fernet token payload."); | |
109 | } | |
110 |
1
1. validateAndDecrypt : mutated return of Object value for com/macasaet/fernet/Validator::validateAndDecrypt to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return object; |
111 | } | |
112 | ||
113 | /** | |
114 | * Check the validity of a token against a pool of keys. This is useful if your application uses key rotation. Since | |
115 | * token-verification is entirely CPU-bound, an attempt is made to evaluate the keys in parallel based on the | |
116 | * available number of processors. If you wish to control the number of parallel threads used, invoke this inside a | |
117 | * custom {@link ForkJoinPool}. | |
118 | * | |
119 | * @param keys | |
120 | * all the non-expired keys that could have been used to generate a token | |
121 | * @param token | |
122 | * the client-provided token of unknown validity | |
123 | * @return the deserialised contents of the token | |
124 | * @throws TokenValidationException | |
125 | * if the token was not generated using any of the supplied keys. | |
126 | */ | |
127 | @SuppressWarnings("PMD.LawOfDemeter") | |
128 | default T validateAndDecrypt(final Collection<? extends Key> keys, final Token token) { | |
129 | final Key key = | |
130 | keys.parallelStream() | |
131 | .filter(token::isValidSignature) | |
132 | .findFirst() | |
133 |
1
1. lambda$validateAndDecrypt$1 : mutated return of Object value for com/macasaet/fernet/Validator::lambda$validateAndDecrypt$1 to ( if (x != null) null else throw new RuntimeException ) → KILLED |
.orElseThrow(() -> new TokenValidationException("Encryption key not found.")); |
134 |
1
1. validateAndDecrypt : mutated return of Object value for com/macasaet/fernet/Validator::validateAndDecrypt to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return validateAndDecrypt(key, token); |
135 | } | |
136 | ||
137 | } | |
Mutations | ||
51 |
1.1 |
|
63 |
1.1 |
|
74 |
1.1 |
|
83 |
1.1 2.2 |
|
106 |
1.1 |
|
107 |
1.1 2.2 3.3 |
|
110 |
1.1 |
|
133 |
1.1 |
|
134 |
1.1 |