FidoU2fAttestationStatementVerifier.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;
26
27
import static com.yubico.webauthn.Crypto.isP256;
28
29
import com.fasterxml.jackson.databind.JsonNode;
30
import com.yubico.internal.util.ExceptionUtil;
31
import com.yubico.webauthn.data.AttestationObject;
32
import com.yubico.webauthn.data.AttestationType;
33
import com.yubico.webauthn.data.AttestedCredentialData;
34
import com.yubico.webauthn.data.ByteArray;
35
import java.io.IOException;
36
import java.security.NoSuchAlgorithmException;
37
import java.security.PublicKey;
38
import java.security.cert.CertificateException;
39
import java.security.cert.X509Certificate;
40
import java.security.interfaces.ECPublicKey;
41
import java.security.spec.InvalidKeySpecException;
42
import java.util.Optional;
43
import lombok.extern.slf4j.Slf4j;
44
45
@Slf4j
46
final class FidoU2fAttestationStatementVerifier
47
    implements AttestationStatementVerifier, X5cAttestationStatementVerifier {
48
49
  private X509Certificate getAttestationCertificate(AttestationObject attestationObject)
50
      throws CertificateException {
51 1 1. getAttestationCertificate : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationCertificate → KILLED
    return getX5cAttestationCertificate(attestationObject)
52
        .map(
53
            attestationCertificate -> {
54 1 1. lambda$getAttestationCertificate$0 : negated conditional → KILLED
              if ("EC".equals(attestationCertificate.getPublicKey().getAlgorithm())
55 1 1. lambda$getAttestationCertificate$0 : negated conditional → KILLED
                  && isP256(((ECPublicKey) attestationCertificate.getPublicKey()).getParams())) {
56 1 1. lambda$getAttestationCertificate$0 : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$getAttestationCertificate$0 → KILLED
                return attestationCertificate;
57
              } else {
58
                throw new IllegalArgumentException(
59
                    "Attestation certificate for fido-u2f must have an ECDSA P-256 public key.");
60
              }
61
            })
62
        .orElseThrow(
63
            () ->
64 1 1. lambda$getAttestationCertificate$1 : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$getAttestationCertificate$1 → NO_COVERAGE
                new IllegalArgumentException(
65
                    "fido-u2f attestation statement must have an \"x5c\" property set to an array of at least one DER encoded X.509 certificate."));
66
  }
67
68
  private static boolean validSelfSignature(X509Certificate cert) {
69
    try {
70 1 1. validSelfSignature : removed call to java/security/cert/X509Certificate::verify → SURVIVED
      cert.verify(cert.getPublicKey());
71 1 1. validSelfSignature : replaced boolean return with false for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::validSelfSignature → KILLED
      return true;
72
    } catch (Exception e) {
73 1 1. validSelfSignature : replaced boolean return with true for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::validSelfSignature → SURVIVED
      return false;
74
    }
75
  }
76
77
  private static ByteArray getRawUserPublicKey(AttestationObject attestationObject)
78
      throws IOException {
79
    final ByteArray pubkeyCose =
80
        attestationObject
81
            .getAuthenticatorData()
82
            .getAttestedCredentialData()
83
            .get()
84
            .getCredentialPublicKey();
85
    final PublicKey pubkey;
86
    try {
87
      pubkey = WebAuthnCodecs.importCosePublicKey(pubkeyCose);
88
    } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
89
      throw ExceptionUtil.wrapAndLog(log, "Failed to decode public key: " + pubkeyCose, e);
90
    }
91
92
    final ECPublicKey ecPubkey;
93
    try {
94
      ecPubkey = (ECPublicKey) pubkey;
95
    } catch (ClassCastException e) {
96
      throw new RuntimeException("U2F supports only EC keys, was: " + pubkey);
97
    }
98
99 1 1. getRawUserPublicKey : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getRawUserPublicKey → KILLED
    return WebAuthnCodecs.ecPublicKeyToRaw(ecPubkey);
100
  }
101
102
  @Override
103
  public AttestationType getAttestationType(AttestationObject attestationObject)
104
      throws IOException, CertificateException {
105
    X509Certificate attestationCertificate = getAttestationCertificate(attestationObject);
106
107 1 1. getAttestationType : negated conditional → KILLED
    if (attestationCertificate.getPublicKey() instanceof ECPublicKey
108 1 1. getAttestationType : negated conditional → KILLED
        && validSelfSignature(attestationCertificate)
109
        && getRawUserPublicKey(attestationObject)
110 1 1. getAttestationType : negated conditional → KILLED
            .equals(
111
                WebAuthnCodecs.ecPublicKeyToRaw(
112
                    (ECPublicKey) attestationCertificate.getPublicKey()))) {
113 1 1. getAttestationType : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationType → KILLED
      return AttestationType.SELF_ATTESTATION;
114
    } else {
115 1 1. getAttestationType : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationType → KILLED
      return AttestationType.BASIC;
116
    }
117
  }
118
119
  @Override
120
  public boolean verifyAttestationSignature(
121
      AttestationObject attestationObject, ByteArray clientDataJsonHash) {
122
    final X509Certificate attestationCertificate;
123
    try {
124
      attestationCertificate = getAttestationCertificate(attestationObject);
125
    } catch (CertificateException e) {
126
      throw new IllegalArgumentException(
127
          String.format(
128
              "Failed to parse X.509 certificate from attestation object: %s", attestationObject));
129
    }
130
131 1 1. verifyAttestationSignature : negated conditional → KILLED
    if (!("EC".equals(attestationCertificate.getPublicKey().getAlgorithm())
132 1 1. verifyAttestationSignature : negated conditional → KILLED
        && isP256(((ECPublicKey) attestationCertificate.getPublicKey()).getParams()))) {
133
      throw new IllegalArgumentException(
134
          "Attestation certificate for fido-u2f must have an ECDSA P-256 public key.");
135
    }
136
137
    final Optional<AttestedCredentialData> attData =
138
        attestationObject.getAuthenticatorData().getAttestedCredentialData();
139
140 2 1. verifyAttestationSignature : replaced boolean return with false for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::verifyAttestationSignature → KILLED
2. verifyAttestationSignature : replaced boolean return with true for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::verifyAttestationSignature → KILLED
    return attData
141
        .map(
142
            attestedCredentialData -> {
143
              JsonNode signature = attestationObject.getAttestationStatement().get("sig");
144
145 1 1. lambda$verifyAttestationSignature$2 : negated conditional → KILLED
              if (signature == null) {
146
                throw new IllegalArgumentException(
147
                    "fido-u2f attestation statement must have a \"sig\" property set to a DER encoded signature.");
148
              }
149
150 1 1. lambda$verifyAttestationSignature$2 : negated conditional → KILLED
              if (signature.isBinary()) {
151
                final ByteArray userPublicKey;
152
153
                try {
154
                  userPublicKey = getRawUserPublicKey(attestationObject);
155
                } catch (IOException e) {
156
                  RuntimeException err =
157
                      new RuntimeException(
158
                          String.format(
159
                              "Failed to parse public key from attestation data %s",
160
                              attestedCredentialData),
161
                          e);
162
                  log.error(err.getMessage(), err);
163
                  throw err;
164
                }
165
166
                ByteArray keyHandle = attestedCredentialData.getCredentialId();
167
168
                U2fRawRegisterResponse u2fRegisterResponse;
169
                try {
170
                  u2fRegisterResponse =
171
                      new U2fRawRegisterResponse(
172
                          userPublicKey,
173
                          keyHandle,
174
                          attestationCertificate,
175
                          new ByteArray(signature.binaryValue()));
176
                } catch (IOException e) {
177
                  RuntimeException err =
178
                      new RuntimeException(
179
                          "signature.isBinary() was true but signature.binaryValue() failed", e);
180
                  log.error(err.getMessage(), err);
181
                  throw err;
182
                }
183
184 2 1. lambda$verifyAttestationSignature$2 : replaced Boolean return with False for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$2 → KILLED
2. lambda$verifyAttestationSignature$2 : replaced Boolean return with True for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$2 → KILLED
                return u2fRegisterResponse.verifySignature(
185
                    attestationObject.getAuthenticatorData().getRpIdHash(), clientDataJsonHash);
186
              } else {
187
                throw new IllegalArgumentException(
188
                    "\"sig\" property of fido-u2f attestation statement must be a CBOR byte array value.");
189
              }
190
            })
191
        .orElseThrow(
192
            () ->
193 1 1. lambda$verifyAttestationSignature$3 : replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$3 → NO_COVERAGE
                new IllegalArgumentException(
194
                    "Attestation object for credential creation must have attestation data."));
195
  }
196
}

Mutations

51

1.1
Location : getAttestationCertificate
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationCertificate → KILLED

54

1.1
Location : lambda$getAttestationCertificate$0
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

55

1.1
Location : lambda$getAttestationCertificate$0
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

56

1.1
Location : lambda$getAttestationCertificate$0
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$getAttestationCertificate$0 → KILLED

64

1.1
Location : lambda$getAttestationCertificate$1
Killed by : none
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$getAttestationCertificate$1 → NO_COVERAGE

70

1.1
Location : validSelfSignature
Killed by : none
removed call to java/security/cert/X509Certificate::verify → SURVIVED

71

1.1
Location : validSelfSignature
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced boolean return with false for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::validSelfSignature → KILLED

73

1.1
Location : validSelfSignature
Killed by : none
replaced boolean return with true for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::validSelfSignature → SURVIVED

99

1.1
Location : getRawUserPublicKey
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getRawUserPublicKey → KILLED

107

1.1
Location : getAttestationType
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
negated conditional → KILLED

108

1.1
Location : getAttestationType
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
negated conditional → KILLED

110

1.1
Location : getAttestationType
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
negated conditional → KILLED

113

1.1
Location : getAttestationType
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationType → KILLED

115

1.1
Location : getAttestationType
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::getAttestationType → KILLED

131

1.1
Location : verifyAttestationSignature
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

132

1.1
Location : verifyAttestationSignature
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

140

1.1
Location : verifyAttestationSignature
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced boolean return with false for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::verifyAttestationSignature → KILLED

2.2
Location : verifyAttestationSignature
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced boolean return with true for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::verifyAttestationSignature → KILLED

145

1.1
Location : lambda$verifyAttestationSignature$2
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

150

1.1
Location : lambda$verifyAttestationSignature$2
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

184

1.1
Location : lambda$verifyAttestationSignature$2
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
replaced Boolean return with False for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$2 → KILLED

2.2
Location : lambda$verifyAttestationSignature$2
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced Boolean return with True for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$2 → KILLED

193

1.1
Location : lambda$verifyAttestationSignature$3
Killed by : none
replaced return value with null for com/yubico/webauthn/FidoU2fAttestationStatementVerifier::lambda$verifyAttestationSignature$3 → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.15.0