PublicKeyCredential.java

1
// Copyright (c) 2018, Yubico AB
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
6
//
7
// 1. Redistributions of source code must retain the above copyright notice, this
8
//    list of conditions and the following disclaimer.
9
//
10
// 2. Redistributions in binary form must reproduce the above copyright notice,
11
//    this list of conditions and the following disclaimer in the documentation
12
//    and/or other materials provided with the distribution.
13
//
14
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
25
package com.yubico.webauthn.data;
26
27
import com.fasterxml.jackson.annotation.JsonCreator;
28
import com.fasterxml.jackson.annotation.JsonProperty;
29
import com.fasterxml.jackson.core.type.TypeReference;
30
import com.yubico.internal.util.JacksonCodecs;
31
import com.yubico.webauthn.AssertionResult;
32
import com.yubico.webauthn.FinishAssertionOptions;
33
import com.yubico.webauthn.FinishRegistrationOptions;
34
import com.yubico.webauthn.RegistrationResult;
35
import com.yubico.webauthn.RelyingParty;
36
import java.io.IOException;
37
import java.util.Optional;
38
import lombok.AllArgsConstructor;
39
import lombok.Builder;
40
import lombok.NonNull;
41
import lombok.Value;
42
43
/**
44
 * The PublicKeyCredential interface inherits from Credential <a
45
 * href="https://www.w3.org/TR/credential-management-1/">[CREDENTIAL-MANAGEMENT-1]</a>, and contains
46
 * the attributes that are returned to the caller when a new credential is created, or a new
47
 * assertion is requested.
48
 *
49
 * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#iface-pkcredential">§5.1.
50
 *     PublicKeyCredential Interface</a>
51
 */
52
@Value
53
@Builder(toBuilder = true)
54
public class PublicKeyCredential<
55
    A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {
56
57
  /**
58
   * The raw Credential ID of this credential, corresponding to the <code>rawId</code> attribute in
59
   * the WebAuthn API.
60
   */
61
  @NonNull private final ByteArray id;
62
63
  /**
64
   * The authenticator's response to the client’s request to either create a public key credential,
65
   * or generate an authentication assertion.
66
   *
67
   * <p>If the {@link PublicKeyCredential} was created in response to <code>
68
   * navigator.credentials.create()</code>, this attribute’s value will be an {@link
69
   * AuthenticatorAttestationResponse}, otherwise, the {@link PublicKeyCredential} was created in
70
   * response to <code>navigator.credentials.get()</code>, and this attribute’s value will be an
71
   * {@link AuthenticatorAssertionResponse}.
72
   */
73
  @NonNull private final A response;
74
75
  private final AuthenticatorAttachment authenticatorAttachment;
76
77
  /**
78
   * A map containing extension identifier → client extension output entries produced by the
79
   * extension’s client extension processing.
80
   */
81
  @NonNull private final B clientExtensionResults;
82
83
  /** The {@link PublicKeyCredential}'s type value is the string "public-key". */
84
  @NonNull @Builder.Default
85
  private final PublicKeyCredentialType type = PublicKeyCredentialType.PUBLIC_KEY;
86
87
  @JsonCreator
88
  private PublicKeyCredential(
89
      @JsonProperty("id") ByteArray id,
90
      @JsonProperty("rawId") ByteArray rawId,
91 1 1. <init> : negated conditional → KILLED
      @NonNull @JsonProperty("response") A response,
92
      @JsonProperty("authenticatorAttachment") AuthenticatorAttachment authenticatorAttachment,
93 1 1. <init> : negated conditional → KILLED
      @NonNull @JsonProperty("clientExtensionResults") B clientExtensionResults,
94 1 1. <init> : negated conditional → KILLED
      @NonNull @JsonProperty("type") PublicKeyCredentialType type) {
95 2 1. <init> : negated conditional → KILLED
2. <init> : negated conditional → KILLED
    if (id == null && rawId == null) {
96
      throw new NullPointerException("At least one of \"id\" and \"rawId\" must be non-null.");
97
    }
98 3 1. <init> : negated conditional → KILLED
2. <init> : negated conditional → KILLED
3. <init> : negated conditional → KILLED
    if (id != null && rawId != null && !id.equals(rawId)) {
99
      throw new IllegalArgumentException(
100
          String.format("\"id\" and \"rawId\" are not equal: %s != %s", id, rawId));
101
    }
102
103 1 1. <init> : negated conditional → KILLED
    this.id = id == null ? rawId : id;
104
    this.response = response;
105
    this.authenticatorAttachment = authenticatorAttachment;
106
    this.clientExtensionResults = clientExtensionResults;
107
    this.type = type;
108
  }
109
110
  private PublicKeyCredential(
111
      ByteArray id,
112 1 1. <init> : negated conditional → KILLED
      @NonNull A response,
113
      AuthenticatorAttachment authenticatorAttachment,
114 1 1. <init> : negated conditional → KILLED
      @NonNull B clientExtensionResults,
115 1 1. <init> : negated conditional → KILLED
      @NonNull PublicKeyCredentialType type) {
116
    this(id, null, response, authenticatorAttachment, clientExtensionResults, type);
117
  }
118
119
  /**
120
   * The <a href="https://w3c.github.io/webauthn/#authenticator-attachment-modality">authenticator
121
   * attachment modality</a> in effect at the time the credential was created or used.
122
   *
123
   * <p>If parsed from JSON, this will be present if and only if the input was a valid value of
124
   * {@link AuthenticatorAttachment}.
125
   *
126
   * <p>The same value will also be available via {@link
127
   * RegistrationResult#getAuthenticatorAttachment()} or {@link
128
   * AssertionResult#getAuthenticatorAttachment()} on the result from {@link
129
   * RelyingParty#finishRegistration(FinishRegistrationOptions)} or {@link
130
   * RelyingParty#finishAssertion(FinishAssertionOptions)}.
131
   *
132
   * @see RegistrationResult#getAuthenticatorAttachment()
133
   * @see AssertionResult#getAuthenticatorAttachment()
134
   * @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
135
   *     the standard matures.
136
   */
137
  @Deprecated
138
  public Optional<AuthenticatorAttachment> getAuthenticatorAttachment() {
139 1 1. getAuthenticatorAttachment : replaced return value with Optional.empty for com/yubico/webauthn/data/PublicKeyCredential::getAuthenticatorAttachment → KILLED
    return Optional.ofNullable(authenticatorAttachment);
140
  }
141
142
  public static <A extends AuthenticatorResponse, B extends ClientExtensionOutputs>
143
      PublicKeyCredentialBuilder<A, B>.MandatoryStages builder() {
144 1 1. builder : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::builder → KILLED
    return new PublicKeyCredentialBuilder<A, B>().start();
145
  }
146
147
  public static class PublicKeyCredentialBuilder<
148
      A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {
149
    private MandatoryStages start() {
150 1 1. start : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder::start → KILLED
      return new MandatoryStages(this);
151
    }
152
153
    @AllArgsConstructor
154
    public class MandatoryStages {
155
      private final PublicKeyCredentialBuilder<A, B> builder;
156
157
      /**
158
       * {@link PublicKeyCredentialBuilder#id(ByteArray) id} is a required parameter.
159
       *
160
       * @see PublicKeyCredentialBuilder#id(ByteArray)
161
       */
162
      public Step2 id(ByteArray id) {
163
        builder.id(id);
164 1 1. id : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages::id → KILLED
        return new Step2();
165
      }
166
167
      public class Step2 {
168
        /**
169
         * {@link PublicKeyCredentialBuilder#response(AuthenticatorResponse) response} is a required
170
         * parameter.
171
         *
172
         * @see PublicKeyCredentialBuilder#response(AuthenticatorResponse)
173
         */
174
        public Step3 response(A response) {
175
          builder.response(response);
176 1 1. response : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages$Step2::response → KILLED
          return new Step3();
177
        }
178
      }
179
180
      public class Step3 {
181
        /**
182
         * {@link PublicKeyCredentialBuilder#clientExtensionResults(ClientExtensionOutputs)
183
         * clientExtensionResults} is a required parameter.
184
         *
185
         * @see PublicKeyCredentialBuilder#clientExtensionResults(ClientExtensionOutputs)
186
         */
187
        public PublicKeyCredentialBuilder<A, B> clientExtensionResults(B clientExtensionResults) {
188 1 1. clientExtensionResults : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages$Step3::clientExtensionResults → KILLED
          return builder.clientExtensionResults(clientExtensionResults);
189
        }
190
      }
191
    }
192
  }
193
194
  /**
195
   * Parse a {@link PublicKeyCredential} object from JSON.
196
   *
197
   * <p>The <code>json</code> should be of the following format:
198
   *
199
   * <pre>
200
   * {
201
   *   "id": "(resp.id)",
202
   *   "response": {
203
   *     "attestationObject": "(Base64Url encoded resp.attestationObject)",
204
   *     "clientDataJSON": "(Base64Url encoded resp.clientDataJSON)"
205
   *   },
206
   *   "clientExtensionResults": { (resp.getClientExtensionResults()) },
207
   *   "type": "public-key"
208
   * }
209
   * </pre>
210
   *
211
   * <dl>
212
   *   <dt>resp:
213
   *   <dd>The <a
214
   *       href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#iface-pkcredential">PublicKeyCredential</a>
215
   *       object returned from a registration ceremony.
216
   *   <dt>id:
217
   *   <dd>The string value of <code>resp.id</code>
218
   *   <dt>response.attestationObject:
219
   *   <dd>The value of <code>resp.attestationObject</code>, Base64Url encoded as a string
220
   *   <dt>response.clientDataJSON:
221
   *   <dd>The value of <code>resp.clientDataJSON</code>, Base64Url encoded as a string
222
   *   <dt>clientExtensionResults:
223
   *   <dd>The return value of <code>resp.getClientExtensionResults()</code>
224
   *   <dt>type:
225
   *   <dd>The literal string value <code>"public-key"</code>
226
   * </dl>
227
   *
228
   * @param json a JSON string of the above format
229
   * @throws IOException if the <code>json</code> is invalid or cannot be decoded as a {@link
230
   *     PublicKeyCredential}
231
   */
232
  public static PublicKeyCredential<
233
          AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
234
      parseRegistrationResponseJson(String json) throws IOException {
235 1 1. parseRegistrationResponseJson : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::parseRegistrationResponseJson → KILLED
    return JacksonCodecs.json()
236
        .readValue(
237
            json,
238
            new TypeReference<
239
                PublicKeyCredential<
240
                    AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>>() {});
241
  }
242
243
  /**
244
   * Parse a {@link PublicKeyCredential} object from JSON.
245
   *
246
   * <p>The <code>json</code> should be of the following format:
247
   *
248
   * <pre>
249
   * {
250
   *   "id": "(resp.id)",
251
   *   "response": {
252
   *     "authenticatorData": "(Base64Url encoded resp.authenticatorData)",
253
   *     "signature": "(Base64Url encoded resp.signature)",
254
   *     "clientDataJSON": "(Base64Url encoded resp.clientDataJSON)",
255
   *     "userHandle": "(null, undefined or Base64Url encoded resp.userHandle)"
256
   *   },
257
   *   "clientExtensionResults": { (resp.getClientExtensionResults()) },
258
   *   "type": "public-key"
259
   * }
260
   * </pre>
261
   *
262
   * <dl>
263
   *   <dt>resp:
264
   *   <dd>The <a
265
   *       href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#iface-pkcredential">PublicKeyCredential</a>
266
   *       object returned from an authentication ceremony.
267
   *   <dt>id:
268
   *   <dd>The string value of <code>resp.id</code>
269
   *   <dt>response.authenticatorData:
270
   *   <dd>The value of <code>resp.authenticatorData</code>, Base64Url encoded as a string
271
   *   <dt>response.signature:
272
   *   <dd>The value of <code>resp.signature</code>, Base64Url encoded as a string
273
   *   <dt>response.clientDataJSON:
274
   *   <dd>The value of <code>resp.clientDataJSON</code>, Base64Url encoded as a string
275
   *   <dt>response.userHandle:
276
   *   <dd>The value of <code>resp.userHandle</code> Base64Url encoded as a string if present,
277
   *       otherwise <code>null</code> or <code>undefined</code>
278
   *   <dt>clientExtensionResults:
279
   *   <dd>The return value of <code>resp.getClientExtensionResults()</code>
280
   *   <dt>type:
281
   *   <dd>The literal string value <code>"public-key"</code>
282
   * </dl>
283
   *
284
   * @param json a JSON string of the above format
285
   * @throws IOException if the <code>json</code> is invalid or cannot be decoded as a {@link
286
   *     PublicKeyCredential}
287
   */
288
  public static PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
289
      parseAssertionResponseJson(String json) throws IOException {
290 1 1. parseAssertionResponseJson : replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::parseAssertionResponseJson → KILLED
    return JacksonCodecs.json()
291
        .readValue(
292
            json,
293
            new TypeReference<
294
                PublicKeyCredential<
295
                    AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>>() {});
296
  }
297
}

Mutations

91

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

93

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

94

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

95

1.1
Location : <init>
Killed by : com.yubico.webauthn.data.JsonIoSpec
negated conditional → KILLED

2.2
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

98

1.1
Location : <init>
Killed by : com.yubico.webauthn.data.JsonIoSpec
negated conditional → KILLED

2.2
Location : <init>
Killed by : com.yubico.webauthn.data.JsonIoSpec
negated conditional → KILLED

3.3
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

103

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

112

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

114

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

115

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

139

1.1
Location : getAuthenticatorAttachment
Killed by : com.yubico.webauthn.data.JsonIoSpec
replaced return value with Optional.empty for com/yubico/webauthn/data/PublicKeyCredential::getAuthenticatorAttachment → KILLED

144

1.1
Location : builder
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::builder → KILLED

150

1.1
Location : start
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder::start → KILLED

164

1.1
Location : id
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages::id → KILLED

176

1.1
Location : response
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages$Step2::response → KILLED

188

1.1
Location : clientExtensionResults
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential$PublicKeyCredentialBuilder$MandatoryStages$Step3::clientExtensionResults → KILLED

235

1.1
Location : parseRegistrationResponseJson
Killed by : com.yubico.webauthn.data.ExtensionsSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::parseRegistrationResponseJson → KILLED

290

1.1
Location : parseAssertionResponseJson
Killed by : com.yubico.webauthn.data.ExtensionsSpec
replaced return value with null for com/yubico/webauthn/data/PublicKeyCredential::parseAssertionResponseJson → KILLED

Active mutators

Tests examined


Report generated by PIT 1.15.0