Validator.java

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 &copy; 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
Location : getClock
Killed by : com.macasaet.fernet.TokenTest.testGenerate(com.macasaet.fernet.TokenTest)
mutated return of Object value for com/macasaet/fernet/Validator::getClock to ( if (x != null) null else throw new RuntimeException ) → KILLED

63

1.1
Location : getTimeToLive
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextRetained(com.macasaet.fernet.ValidatorTest)
mutated return of Object value for com/macasaet/fernet/Validator::getTimeToLive to ( if (x != null) null else throw new RuntimeException ) → KILLED

74

1.1
Location : getMaxClockSkew
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextRetained(com.macasaet.fernet.ValidatorTest)
mutated return of Object value for com/macasaet/fernet/Validator::getMaxClockSkew to ( if (x != null) null else throw new RuntimeException ) → KILLED

83

1.1
Location : getObjectValidator
Killed by : com.macasaet.fernet.TokenTest.testGenerate(com.macasaet.fernet.TokenTest)
mutated return of Object value for com/macasaet/fernet/Validator::getObjectValidator to ( if (x != null) null else throw new RuntimeException ) → KILLED

2.2
Location : lambda$getObjectValidator$0
Killed by : com.macasaet.fernet.TokenTest.testGenerate(com.macasaet.fernet.TokenTest)
replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED

106

1.1
Location : validateAndDecrypt
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextRetained(com.macasaet.fernet.ValidatorTest)
negated conditional → KILLED

107

1.1
Location : validateAndDecrypt
Killed by : none
changed conditional boundary → SURVIVED

2.2
Location : validateAndDecrypt
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextClearedOnValidationFailure(com.macasaet.fernet.ValidatorTest)
Changed increment from -1 to 1 → KILLED

3.3
Location : validateAndDecrypt
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextClearedOnValidationFailure(com.macasaet.fernet.ValidatorTest)
negated conditional → KILLED

110

1.1
Location : validateAndDecrypt
Killed by : com.macasaet.fernet.ValidatorTest.verifyPlainTextRetained(com.macasaet.fernet.ValidatorTest)
mutated return of Object value for com/macasaet/fernet/Validator::validateAndDecrypt to ( if (x != null) null else throw new RuntimeException ) → KILLED

133

1.1
Location : lambda$validateAndDecrypt$1
Killed by : com.macasaet.fernet.TokenTest.verifyExceptionThrownWhenKeyNoLongerInRotation(com.macasaet.fernet.TokenTest)
mutated return of Object value for com/macasaet/fernet/Validator::lambda$validateAndDecrypt$1 to ( if (x != null) null else throw new RuntimeException ) → KILLED

134

1.1
Location : validateAndDecrypt
Killed by : com.macasaet.fernet.TokenTest.verifyKeyInRotationCanDecryptToken(com.macasaet.fernet.TokenTest)
mutated return of Object value for com/macasaet/fernet/Validator::validateAndDecrypt to ( if (x != null) null else throw new RuntimeException ) → KILLED

Active mutators

Tests examined


Report generated by PIT 1.4.10