|
| 1 | +<!-- @import "[TOC]" {cmd="toc" depthFrom=2 depthTo=6 orderedList=false} --> |
| 2 | + |
| 3 | +# web-eid-authtoken-validation-java |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +The Web eID authentication token validation library for Java allows validating |
| 8 | +Web eID JWT authentication tokens during authentication in web applications. |
| 9 | + |
| 10 | +# Quickstart |
| 11 | + |
| 12 | +Complete the steps below to add strong authentication support to your web application back end. |
| 13 | + |
| 14 | +To run this quickstart you need a Java web application that uses Maven or Gradle to manage packages. |
| 15 | + |
| 16 | +See full example [here](). |
| 17 | + |
| 18 | +## 1. Add the library to your Maven or Gradle project |
| 19 | + |
| 20 | +Add the following lines to Maven `pom.xml`: |
| 21 | + |
| 22 | +```xml |
| 23 | + <dependency> |
| 24 | + <groupId>org.webeid.security</groupId> |
| 25 | + <artifactId>authtoken-validation</artifactId> |
| 26 | + <version>1.0.0-SNAPSHOT</version> |
| 27 | + </dependency> |
| 28 | + |
| 29 | + <repositories> |
| 30 | + <repository> |
| 31 | + <id>gitlab-maven</id> |
| 32 | + <url>https://gitlab.com/api/v4/projects/19948337/packages/maven</url> |
| 33 | + </repository> |
| 34 | + </repositories> |
| 35 | +``` |
| 36 | + |
| 37 | +## 2. Add cache support |
| 38 | + |
| 39 | +## 3. Add trusted certificate authorities |
| 40 | + |
| 41 | +## 4. Add REST endpoints for the authentication requests |
| 42 | + |
| 43 | +## 5. Add authentication token validation |
| 44 | + |
| 45 | + |
| 46 | +# Introduction |
| 47 | + |
| 48 | +This library has everything that it takes to ensure that the authentication token sent by the Web-eID browser extension contains valid data. And that this data is consistent and was not modified in-between by the third party. It is easy to configure and to integrate into your authentication service. |
| 49 | + |
| 50 | +The library is designed to take advantage of the so-called "builder" pattern to separate the configuration and execution parts from each other. |
| 51 | + |
| 52 | +# Token validation |
| 53 | + |
| 54 | +The token validation process consists of three stages: |
| 55 | + |
| 56 | +- Firstly, the **token header** gets parsed and the user certificate is extracted from the *x5c* field. Then the certificate is checked for validity, expiration and purpose. Also, an optional OCSP check is executed. |
| 57 | +- Secondly, if the user certificate is valid and has a suitable purpose, the **token signature** is checked for validity. |
| 58 | +- Lastly, the **token body** gets parsed. *Nonce* and *Origin* fields get validated. Also, an optional service certificate fingerprint check is executed. |
| 59 | + |
| 60 | +## Basic usage |
| 61 | + |
| 62 | +The builder class need a *javax.cache.Cache* instance (use *Hazelcast* or *Infinispan* if you do use a cluster, or *Caffeine* if you don't): |
| 63 | +```java |
| 64 | +Cache<String, Nonce> cache = // TODO: create new cache instance here |
| 65 | +``` |
| 66 | +You will also need to provide issuer certificates: |
| 67 | +```java |
| 68 | +X509Certificate[] trustedCertificateAuthorities = // TODO: load trusted issuer certs |
| 69 | +``` |
| 70 | +The **cache** instance is used to look up the nonce object using its unique value as a search key. The values in the cache are populated by the nonce generator (which is described in detail in the *Nonce generation* chapter), while the **trustedCertificateAuthorities** certificates are used to validate the user certificate's trust chain. |
| 71 | + |
| 72 | +The simplest way to create a validator instance is to use the builder class with a minimal set of mandatory parameters: |
| 73 | +```java |
| 74 | +AuthTokenValidator validator = new AuthTokenValidatorBuilder("https://my.origin.address") |
| 75 | + .withNonceCache(cache) |
| 76 | + .withTrustedCertificateAuthorities(trustedCertificateAuthorities) |
| 77 | + .build(); |
| 78 | + X509Certificate userCertificate = tokenValidator.validate(myTokenString); |
| 79 | +``` |
| 80 | + |
| 81 | +## Configuration |
| 82 | +Additional configuration is possible for the builder class: |
| 83 | + |
| 84 | +- `withCertificateFingerprint(String)` - certificate fingerprint validation is disabled by default, but can be enabled. |
| 85 | +- `withoutCertificateRevocationValidation()` - disables certificate OCSP validation, which is enabled by default. |
| 86 | +- `withAllowedClockSkew(Long)` - allows clock skew in seconds during token parsing process. Default value is **180L**, which corresponds to 3 minutes. |
| 87 | + |
| 88 | +Example: |
| 89 | +```java |
| 90 | +AuthTokenValidator validator = new AuthTokenValidatorBuilder("https://my.origin.address") |
| 91 | + .withNonceCache(cache) |
| 92 | + .withTrustedCertificateAuthorities(trustedCertificateAuthorities) |
| 93 | + .withCertificateFingerprint("urn:cert:sha-256:fingerprint-hash-goes-here") |
| 94 | + .withoutCertificateRevocationValidation() |
| 95 | + .withAllowedClockSkew(3600L) |
| 96 | + .build(); |
| 97 | + |
| 98 | +X509Certificate userCertificate = tokenValidator.validate(myTokenString); |
| 99 | +``` |
| 100 | + |
| 101 | +### Certificate fingerprint |
| 102 | +Due to the technical limitation of Web Browsers, certificate fingerprint validation currently works only when Firefox browser is used. |
| 103 | + |
| 104 | +## What gets validated |
| 105 | +The token validation process covers different aspects. It ensures, that: |
| 106 | + |
| 107 | +- **token header** is valid, contains a valid and trusted certificate, which has not expired and has a proper purpose. |
| 108 | +- **token signature** is not empty, is valid and was created using the certificate, that was specified in the header. |
| 109 | +- **token body** is not empty and has meaningful data. |
| 110 | +- **token** has not expired. |
| 111 | +- **nonce value**, received from the client-side, has the corresponding nonce object in the cache, which has not expired. |
| 112 | +- **Origin URL** is valid and matches the *expected Origin URL* set in builder class. |
| 113 | + |
| 114 | +**NB!** `Nonce object` is a `Nonce value` plus `metadata` . To know more about it please refer to the *Nonce generation* chapter. |
| 115 | + |
| 116 | +## Possible validation errors |
| 117 | +There is a set of possible errors that can occur during the validation process: |
| 118 | + |
| 119 | +#### NonceNotFoundException |
| 120 | +Is thrown if the nonce object is not found from the nonce cache using provided nonce value. |
| 121 | +#### NonceExpiredException |
| 122 | +Is thrown if the nonce object is found but has expired. |
| 123 | +#### OriginMismatchException |
| 124 | +Is thrown if origin URL does not match the *expected origin URL* which was set in builder class. |
| 125 | +#### ServiceCertificateFingerprintValidationException |
| 126 | +Is thrown if the service certificate fingerprint validation is enabled, however, the actual fingerprint does not match the *expected certificate fingerprint* which was set in builder class. |
| 127 | +#### TokenExpiredException |
| 128 | +Is thrown if an expired token is detected and the `withAllowedClockSkew` configuration option does not cover the time difference. |
| 129 | +#### TokenParseException |
| 130 | +Is thrown if the token has an invalid format and cannot be parsed. |
| 131 | +#### TokenSignatureValidationException |
| 132 | +Is thrown if the token signature is missing or has an invalid format. |
| 133 | +#### UserCertificateExpiredException |
| 134 | +Is thrown if the user certificate's validity period end date is in the past. |
| 135 | +#### UserCertificateMissingPurposeException |
| 136 | +Is thrown if the purpose of the user certificate is not defined. |
| 137 | +#### UserCertificateNotTrustedException |
| 138 | +Is thrown if the user certificate is not trusted. |
| 139 | +#### UserCertificateNotYetValidException |
| 140 | +Is thrown if the user certificate's validity period start date is in the future. |
| 141 | +#### UserCertificateParseException |
| 142 | +Is thrown if the user certificate cannot be parsed from the token's x5c field. |
| 143 | +#### UserCertificateRevocationCheckFailException |
| 144 | +Is thrown if the user certificate OCSP check has failed. |
| 145 | +#### UserCertificateRevokedException |
| 146 | +Is thrown if the user certificate OCSP check's result is not GOOD. |
| 147 | +#### UserCertificateWrongPurposeException |
| 148 | +Is thrown if according to the user certificate's purpose is not meant to be used for authentication. |
| 149 | + |
| 150 | +## Create your own validator implementation |
| 151 | +It is possible to create a custom implementation of the token validator. To achieve this, you have to: |
| 152 | + |
| 153 | +- Create a new validator class, which extends the `AuthTokenValidator` interface. |
| 154 | +- Create a new builder class, which extends the `AuthTokenValidatorBuilder` class and overrides the `build()` method to create an instance of your new validator class. |
| 155 | + |
| 156 | +**MyCustomTokenValidator.java** |
| 157 | +```java |
| 158 | +class MyCustomTokenValidator implements AuthTokenValidator { |
| 159 | + ... |
| 160 | +} |
| 161 | +``` |
| 162 | +**MyCustomBuilder.java** |
| 163 | +```java |
| 164 | +class MyCustomBuilder extends AuthTokenValidatorBuilder { |
| 165 | + @Override |
| 166 | + public AuthTokenValidator build() { |
| 167 | + ... |
| 168 | + validateParameters(); |
| 169 | + return new MyCustomTokenValidator(...); |
| 170 | + } |
| 171 | + ... |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +Additionally, you can override the `validateParameters()` method in case you need to add new fields and validate them: |
| 176 | + |
| 177 | + |
| 178 | +**MyCustomBuilder.java** |
| 179 | +```java |
| 180 | +class MyCustomBuilder extends AuthTokenValidatorBuilder { |
| 181 | + |
| 182 | + private String myNewField = ""; |
| 183 | + |
| 184 | + private MyCustomBuilder withMyNewField(String myNewField) { |
| 185 | + this.myNewField = myNewField; |
| 186 | + } |
| 187 | + |
| 188 | + @Override |
| 189 | + public AuthTokenValidator build() { |
| 190 | + validateParameters(); |
| 191 | + return new MyCustomTokenValidator(..., myNewField); |
| 192 | + } |
| 193 | + |
| 194 | + @Override |
| 195 | + protected void validateParameters() { |
| 196 | + super.validateParameters(); |
| 197 | + Objects.requireNonNull(myNewField, "My new field must not be null"); |
| 198 | + } |
| 199 | + ... |
| 200 | +} |
| 201 | +``` |
| 202 | +Then use it in your application: |
| 203 | +```java |
| 204 | +MyCustomTokenValidator validator = new MyCustomBuilder("https://my.origin.address") |
| 205 | + .withNonceCache(cache) |
| 206 | + .withTrustedCertificateAuthorities(trustedCertificateAuthorities) |
| 207 | + .withMyNewField("My new field value") |
| 208 | + .build(); |
| 209 | + |
| 210 | +X509Certificate certificate = validator.validate(myTokenString); |
| 211 | +``` |
| 212 | + |
| 213 | +# Nonce generation |
| 214 | +Nonce value generation is implemented similarly to the token validation - it also uses the builder pattern and also requires the cache instance. |
| 215 | + |
| 216 | +## Basic usage |
| 217 | + |
| 218 | +The builder class will need a *javax.cache.Cache* instance (use *Hazelcast* or *Infinispan* if you do use a cluster, or *Caffeine* if you don't): |
| 219 | +```java |
| 220 | +Cache<String, Nonce> cache = // TODO: create new cache instance here |
| 221 | +``` |
| 222 | + |
| 223 | +The **cache** is used store nonce objects. |
| 224 | + |
| 225 | +The simplest way to create a generator instance is to use the builder class with a minimal set of mandatory parameters: |
| 226 | + |
| 227 | +```java |
| 228 | +NonceGenerator generator = new NonceGeneratorBuilder() |
| 229 | + .withNonceCache(cache) |
| 230 | + .build(); |
| 231 | + |
| 232 | +byte[] nonceKey = nonceGenerator.generate(); |
| 233 | +``` |
| 234 | +The`generate()` method also puts the generated nonce object into the provided cache. |
| 235 | + |
| 236 | +## Configuration |
| 237 | +Additional configuration is possible for the builder class: |
| 238 | + |
| 239 | +- `withNonceTtl(int)` - specifies the time-to-live in minutes. Default value is 5. |
| 240 | +- `withSecureRandom(SecureRandom)` - allows to specify a custom [SecureRandom](https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html) class instance. |
| 241 | + |
| 242 | +Example: |
| 243 | +```java |
| 244 | +NonceGenerator generator = new NonceGeneratorBuilder() |
| 245 | + .withNonceCache(cache) |
| 246 | + .withNonceTtl(10) |
| 247 | + .withSecureRandom(myCustomSecureRandomInstance) |
| 248 | + .build(); |
| 249 | + |
| 250 | +byte[] nonceKey = nonceGenerator.generate(); |
| 251 | +``` |
| 252 | +## How it works |
| 253 | +Here are some useful facts: |
| 254 | + |
| 255 | +- Nonce objects are stored into the cache on the server-side and later are looked up from the same cache using the nonce values as keys. |
| 256 | +- Nonce values are sent to the client-side, nonce objects are not. |
| 257 | +- Every nonce value is meant to be unique and as less likely reproducible as possible. |
| 258 | +- Every nonce object is meant to be used only once. |
| 259 | +- Every nonce object is meant to be used before it expires. |
0 commit comments