Search in sources :

Example 1 with AuthenticatorData

use of com.webauthn4j.data.attestation.authenticator.AuthenticatorData in project webauthn4j by webauthn4j.

the class FIDOU2FAuthenticatorAdaptor method register.

@Override
public CredentialCreationResponse register(PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions, CollectedClientData collectedClientData, RegistrationEmulationOption registrationEmulationOption, AttestationOption attestationOption) {
    String rpId = publicKeyCredentialCreationOptions.getRp().getId();
    byte[] rpIdHash = MessageDigestUtil.createSHA256().digest(rpId.getBytes(StandardCharsets.UTF_8));
    byte[] challengeParameter = MessageDigestUtil.createSHA256().digest(collectedClientDataConverter.convertToBytes(collectedClientData));
    // noinspection UnnecessaryLocalVariable
    byte[] applicationParameter = rpIdHash;
    RegistrationRequest registrationRequest = new RegistrationRequest(challengeParameter, applicationParameter);
    RegistrationResponse registrationResponse = fidoU2FAuthenticator.register(registrationRequest, registrationEmulationOption);
    AttestationStatement attestationStatement = new FIDOU2FAttestationStatement(new AttestationCertificatePath(Collections.singletonList(registrationResponse.getAttestationCertificate())), registrationResponse.getSignature());
    EC2COSEKey ec2CredentialPublicKey = EC2COSEKey.createFromUncompressedECCKey(registrationResponse.getUserPublicKey());
    // zero-filled 16bytes(128bits) array
    AAGUID aaguid = AAGUID.ZERO;
    AttestedCredentialData attestedCredentialData = new AttestedCredentialData(aaguid, registrationResponse.getKeyHandle(), ec2CredentialPublicKey);
    byte flag = BIT_AT | BIT_UP;
    AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authenticatorData = new AuthenticatorData<>(rpIdHash, flag, 0, attestedCredentialData);
    AttestationObject attestationObject = new AttestationObject(authenticatorData, attestationStatement);
    return new CredentialCreationResponse(attestationObject);
}
Also used : AttestationCertificatePath(com.webauthn4j.data.attestation.statement.AttestationCertificatePath) AAGUID(com.webauthn4j.data.attestation.authenticator.AAGUID) RegistrationExtensionAuthenticatorOutput(com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput) AttestedCredentialData(com.webauthn4j.data.attestation.authenticator.AttestedCredentialData) AuthenticatorData(com.webauthn4j.data.attestation.authenticator.AuthenticatorData) AttestationObject(com.webauthn4j.data.attestation.AttestationObject) FIDOU2FAttestationStatement(com.webauthn4j.data.attestation.statement.FIDOU2FAttestationStatement) AttestationStatement(com.webauthn4j.data.attestation.statement.AttestationStatement) FIDOU2FAttestationStatement(com.webauthn4j.data.attestation.statement.FIDOU2FAttestationStatement) EC2COSEKey(com.webauthn4j.data.attestation.authenticator.EC2COSEKey) CredentialCreationResponse(com.webauthn4j.test.authenticator.CredentialCreationResponse)

Example 2 with AuthenticatorData

use of com.webauthn4j.data.attestation.authenticator.AuthenticatorData in project webauthn4j by webauthn4j.

the class WebAuthnModelAuthenticator method getAssertion.

public GetAssertionResponse getAssertion(GetAssertionRequest getAssertionRequest, AuthenticationEmulationOption authenticationEmulationOption) {
    byte flags = 0;
    // Check if all the supplied parameters are syntactically well-formed and of the correct length.
    // If not, return an error code equivalent to "UnknownError" and terminate the operation.
    // TODO
    // Let credentialOptions be a new empty set of public key credential sources.
    List<PublicKeyCredentialSource> credentialOptions = new ArrayList<>();
    // If allowCredentialDescriptorList was supplied, then for each descriptor of allowCredentialDescriptorList:
    List<PublicKeyCredentialDescriptor> allowCredentialDescriptorList = getAssertionRequest.getAllowCredentialDescriptorList();
    if (allowCredentialDescriptorList != null && !allowCredentialDescriptorList.isEmpty()) {
        for (PublicKeyCredentialDescriptor credentialDescriptor : getAssertionRequest.getAllowCredentialDescriptorList()) {
            // Let credSource be the result of looking up descriptor.id in this authenticator.
            PublicKeyCredentialSource credSource = lookup(credentialDescriptor.getId());
            if (credSource != null) {
                credentialOptions.add(credSource);
            }
        }
    } else // Otherwise (allowCredentialDescriptorList was not supplied),
    // for each key -> credSource of this authenticator’s credentials map, append credSource to credentialOptions.
    {
        for (Map.Entry<CredentialMapKey, PublicKeyCredentialSource> entry : credentialMap.entrySet()) {
            credentialOptions.add(entry.getValue());
        }
    }
    // Remove any items from credentialOptions whose rpId is not equal to rpId.
    credentialOptions = credentialOptions.stream().filter(item -> item.getRpId().equals(getAssertionRequest.getRpId())).collect(Collectors.toList());
    // If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation.
    if (credentialOptions.isEmpty()) {
        throw new NotAllowedException("No matching authenticator found");
    }
    // If requireUserVerification is true, the method of obtaining user consent MUST include user verification.
    if (getAssertionRequest.isRequireUserVerification()) {
        flags |= BIT_UV;
    }
    // If requireUserPresence is true, the method of obtaining user consent MUST include a test of user presence.
    if (getAssertionRequest.isRequireUserPresence()) {
        flags |= BIT_UP;
    }
    // If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation.
    // TODO
    PublicKeyCredentialSource selectedCredential = credentialOptions.get(0);
    // Let processedExtensions be the result of authenticator extension processing for each supported
    // extension identifier -> authenticator extension input in extensions.
    AuthenticationExtensionsAuthenticatorOutputs<AuthenticationExtensionAuthenticatorOutput> processedExtensions = new AuthenticationExtensionsAuthenticatorOutputs<>();
    if (!processedExtensions.getKeys().isEmpty()) {
        flags |= BIT_ED;
    }
    // Increment the RP ID-associated signature counter or the global signature counter value,
    // depending on which approach is implemented by the authenticator, by some positive value.
    countUp();
    // Let authenticatorData be the byte array specified in §6.1 Authenticator data including processedExtensions,
    // if any, as the extensions and excluding attestedCredentialData.
    byte[] rpIdHash = MessageDigestUtil.createSHA256().digest(getAssertionRequest.getRpId().getBytes(StandardCharsets.UTF_8));
    AuthenticatorData<AuthenticationExtensionAuthenticatorOutput> authenticatorDataObject = new AuthenticatorData<>(rpIdHash, flags, counter, processedExtensions);
    byte[] authenticatorData = authenticatorDataConverter.convert(authenticatorDataObject);
    // Let signature be the assertion signature of the concatenation authenticatorData || hash using
    // the privateKey of selectedCredential as shown in Figure 2, below. A simple, undelimited concatenation is
    // safe to use here because the authenticator data describes its own length.
    // The hash of the serialized client data (which potentially has a variable length) is always the last element.
    byte[] clientDataHash = getAssertionRequest.getHash();
    byte[] signedData = ByteBuffer.allocate(authenticatorData.length + clientDataHash.length).put(authenticatorData).put(clientDataHash).array();
    byte[] signature = TestDataUtil.calculateSignature(selectedCredential.getPrivateKey().getPrivateKey(), signedData);
    // If any error occurred while generating the assertion signature,
    // return an error code equivalent to "UnknownError" and terminate the operation.
    // Return to the user agent:
    GetAssertionResponse getAssertionResponse = new GetAssertionResponse();
    getAssertionResponse.setCredentialId(selectedCredential.getId());
    getAssertionResponse.setAuthenticatorData(authenticatorData);
    getAssertionResponse.setSignature(signature);
    getAssertionResponse.setUserHandle(selectedCredential.getUserHandle());
    return getAssertionResponse;
}
Also used : PublicKeyCredentialDescriptor(com.webauthn4j.data.PublicKeyCredentialDescriptor) AuthenticationExtensionsAuthenticatorOutputs(com.webauthn4j.data.extension.authenticator.AuthenticationExtensionsAuthenticatorOutputs) AuthenticationExtensionAuthenticatorOutput(com.webauthn4j.data.extension.authenticator.AuthenticationExtensionAuthenticatorOutput) AuthenticatorData(com.webauthn4j.data.attestation.authenticator.AuthenticatorData)

Example 3 with AuthenticatorData

use of com.webauthn4j.data.attestation.authenticator.AuthenticatorData in project webauthn4j by webauthn4j.

the class WebAuthnModelAuthenticator method makeCredential.

public MakeCredentialResponse makeCredential(MakeCredentialRequest makeCredentialRequest, RegistrationEmulationOption registrationEmulationOption) {
    PublicKeyCredentialRpEntity rpEntity = makeCredentialRequest.getRpEntity();
    // Check if all the supplied parameters are syntactically well-formed and of the correct length.
    // If not, return an error code equivalent to "UnknownError" and terminate the operation.
    // TODO
    // Check if at least one of the specified combinations of PublicKeyCredentialType and cryptographic parameters
    // in credTypesAndPubKeyAlgs is supported. If not, return an error code equivalent to "NotSupportedError"
    // and terminate the operation.
    Optional<PublicKeyCredentialParameters> optionalPublicKeyCredentialParameters = makeCredentialRequest.getCredTypesAndPublicKeyAlgs().stream().filter(this::isCapableOfHandling).findFirst();
    PublicKeyCredentialParameters publicKeyCredentialParameters;
    if (optionalPublicKeyCredentialParameters.isPresent()) {
        publicKeyCredentialParameters = optionalPublicKeyCredentialParameters.get();
    } else {
        throw new NotSupportedException("Specified PublicKeyCredentialParameters are not supported");
    }
    // For each descriptor of excludeCredentialDescriptorList:
    List<PublicKeyCredentialDescriptor> descriptors = makeCredentialRequest.getExcludeCredentialDescriptorList();
    if (descriptors == null) {
        descriptors = Collections.emptyList();
    }
    for (PublicKeyCredentialDescriptor descriptor : descriptors) {
        PublicKeyCredentialSource publicKeyCredentialSource = lookup(descriptor.getId());
        // The method of obtaining user consent MUST include a test of user presence.
        if (publicKeyCredentialSource != null) {
            if (publicKeyCredentialSource.getRpId().equals(rpEntity.getId()) && publicKeyCredentialSource.getType().equals(descriptor.getType())) {
                boolean userConsent = true;
                // confirms consent to create a new credential
                if (userConsent) {
                    throw new InvalidStateException("");
                } else // does not consent to create a new credential
                {
                    throw new NotAllowedException("User consent is required");
                }
            }
        }
    }
    // return an error code equivalent to "ConstraintError" and terminate the operation.
    if (makeCredentialRequest.isRequireResidentKey() && !isCapableOfStoringClientSideResidentCredential()) {
        throw new ConstraintException("Authenticator isn't capable of storing client-side resident credential");
    }
    // return an error code equivalent to "ConstraintError" and terminate the operation.
    if (makeCredentialRequest.isRequireUserVerification() && !isCapableOfUserVerification()) {
        throw new ConstraintException("Authenticator isn't capable of user verification");
    }
    // Obtain user consent for creating a new credential.
    // The prompt for obtaining this consent is shown by the authenticator if it has its own output capability,
    // or by the user agent otherwise. The prompt SHOULD display rpEntity.id, rpEntity.name, userEntity.name
    // and userEntity.displayName, if possible.
    boolean userVerification = true;
    boolean userConsent = true;
    // "NotAllowedError" and terminate the operation.
    if (makeCredentialRequest.isRequireUserVerification() && !userVerification) {
        throw new NotAllowedException("User is not verified.");
    }
    if (makeCredentialRequest.isRequireUserPresence() && !userConsent) {
        throw new NotAllowedException("User doesn't resolve consent.");
    }
    // Once user consent has been obtained, generate a new credential object:
    byte[] credentialId;
    // Let (publicKey, privateKey) be a new pair of cryptographic keys using the combination of
    // PublicKeyCredentialType and cryptographic parameters represented by the first item in
    // credTypesAndPubKeyAlgs that is supported by this authenticator.
    KeyPair credentialKeyPair;
    COSEKey cosePublicKey;
    COSEKey cosePrivateKey;
    try {
        credentialKeyPair = ECUtil.createKeyPair();
        ECPublicKey publicKey = (ECPublicKey) credentialKeyPair.getPublic();
        ECPrivateKey privateKey = (ECPrivateKey) credentialKeyPair.getPrivate();
        cosePublicKey = TestDataUtil.createEC2COSEPublicKey(publicKey);
        cosePrivateKey = TestDataUtil.createEC2COSEPrivateKey(publicKey, privateKey);
        // Let userHandle be userEntity.id.
        byte[] userHandle = makeCredentialRequest.getUserEntity().getId();
        // Let credentialSource be a new public key credential source with the fields:
        PublicKeyCredentialSource credentialSource = new PublicKeyCredentialSource();
        credentialSource.setType(PublicKeyCredentialType.PUBLIC_KEY);
        credentialSource.setPrivateKey(cosePrivateKey);
        credentialSource.setRpId(rpEntity.getId());
        credentialSource.setUserHandle(userHandle);
        credentialSource.setOtherUI(null);
        // Credential Private Key:
        if (makeCredentialRequest.isRequireResidentKey()) {
            // Let credentialId be a new credential id.
            credentialId = new byte[32];
            secureRandom.nextBytes(credentialId);
            // Set credentialSource.id to credentialId.
            credentialSource.setId(credentialId);
            // Let credentials be this authenticator’s credentials map.
            // noinspection UnnecessaryLocalVariable
            Map<CredentialMapKey, PublicKeyCredentialSource> credentials = credentialMap;
            credentials.put(new CredentialMapKey(rpEntity.getId(), userHandle), credentialSource);
        } else // Otherwise:
        {
            // Let credentialId be the result of serializing and encrypting credentialSource
            // so that only this authenticator can decrypt it.
            byte[] data = cborConverter.writeValueAsBytes(credentialSource);
            credentialId = CipherUtil.encrypt(data, credentialEncryptionKey);
        }
    }// return an error code equivalent to "UnknownError" and terminate the operation.
     catch (RuntimeException e) {
        throw new WebAuthnModelException(e);
    }
    // Let processedExtensions be the result of authenticator extension processing for each
    // supported extension identifier -> authenticator extension input in extensions.
    AuthenticationExtensionsAuthenticatorOutputs<RegistrationExtensionAuthenticatorOutput> registrationExtensionAuthenticatorOutputs = processRegistrationExtensions(makeCredentialRequest);
    // If the authenticator supports:
    // a per-RP ID signature counter
    // allocate the counter, associate it with the RP ID, and initialize the counter value as zero.
    // a global signature counter
    // Use the global signature counter's actual value when generating authenticator data.
    // a per credential signature counter
    // allocate the counter, associate it with the new credential, and initialize the counter value as zero.
    // TODO: counter mode
    countUp();
    // Let attestedCredentialData be the attested credential data byte array including the credentialId and publicKey.
    byte[] rpIdHash = MessageDigestUtil.createSHA256().digest(rpEntity.getId().getBytes(StandardCharsets.UTF_8));
    byte flag = BIT_AT;
    if (userConsent)
        flag |= BIT_UP;
    if (userVerification)
        flag |= BIT_UV;
    if (!registrationExtensionAuthenticatorOutputs.getKeys().isEmpty())
        flag |= BIT_ED;
    AttestedCredentialData attestedCredentialData = new AttestedCredentialData(aaguid, credentialId, cosePublicKey);
    // Let authenticatorData be the byte array specified in §6.1 Authenticator data,
    // including attestedCredentialData as the attestedCredentialData and processedExtensions, if any, as the extensions.
    AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authenticatorData = new AuthenticatorData<>(rpIdHash, flag, counter, attestedCredentialData, registrationExtensionAuthenticatorOutputs);
    byte[] authenticatorDataBytes = authenticatorDataConverter.convert(authenticatorData);
    byte[] signedData = getSignedData(authenticatorDataBytes, makeCredentialRequest.getHash());
    byte[] clientDataHash = makeCredentialRequest.getHash();
    AttestationStatementRequest attestationStatementRequest = new AttestationStatementRequest(signedData, credentialKeyPair, clientDataHash);
    AttestationStatement attestationStatement = createAttestationStatement(attestationStatementRequest, registrationEmulationOption);
    // Return the attestation object for the new credential created by the procedure specified in
    // §6.3.4 Generating an Attestation Object using an authenticator-chosen attestation statement format,
    // authenticatorData, and hash. For more details on attestation, see §6.3 Attestation.
    AttestationObject attestationObject = new AttestationObject(authenticatorData, attestationStatement);
    // On successful completion of this operation, the authenticator returns the attestation object to the client.
    MakeCredentialResponse makeCredentialResponse = new MakeCredentialResponse();
    makeCredentialResponse.setAttestationObject(attestationObject);
    return makeCredentialResponse;
}
Also used : COSEKey(com.webauthn4j.data.attestation.authenticator.COSEKey) RegistrationExtensionAuthenticatorOutput(com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput) AttestedCredentialData(com.webauthn4j.data.attestation.authenticator.AttestedCredentialData) AuthenticatorData(com.webauthn4j.data.attestation.authenticator.AuthenticatorData) AttestationStatement(com.webauthn4j.data.attestation.statement.AttestationStatement) ECPrivateKey(java.security.interfaces.ECPrivateKey) KeyPair(java.security.KeyPair) PublicKeyCredentialRpEntity(com.webauthn4j.data.PublicKeyCredentialRpEntity) PublicKeyCredentialDescriptor(com.webauthn4j.data.PublicKeyCredentialDescriptor) ECPublicKey(java.security.interfaces.ECPublicKey) PublicKeyCredentialParameters(com.webauthn4j.data.PublicKeyCredentialParameters) AttestationObject(com.webauthn4j.data.attestation.AttestationObject)

Example 4 with AuthenticatorData

use of com.webauthn4j.data.attestation.authenticator.AuthenticatorData in project webauthn4j by webauthn4j.

the class AuthenticatorDataConverter method convert.

/**
 * Converts from a byte array to {@link AuthenticatorData}.
 *
 * @param <T>    ExtensionAuthenticatorOutput
 * @param source the source byte array to convert
 * @return the converted object
 */
@NonNull
public <T extends ExtensionAuthenticatorOutput> AuthenticatorData<T> convert(@NonNull byte[] source) {
    try {
        ByteBuffer byteBuffer = ByteBuffer.wrap(source);
        byte[] rpIdHash = new byte[RPID_HASH_LENGTH];
        byteBuffer.get(rpIdHash, 0, RPID_HASH_LENGTH);
        byte flags = byteBuffer.get();
        long counter = UnsignedNumberUtil.getUnsignedInt(byteBuffer);
        AttestedCredentialData attestedCredentialData;
        AuthenticationExtensionsAuthenticatorOutputs<T> extensions;
        if (AuthenticatorData.checkFlagAT(flags)) {
            if (byteBuffer.hasRemaining()) {
                attestedCredentialData = attestedCredentialDataConverter.convert(byteBuffer);
            } else {
                // Apple App Attest API assertion has AT flag even though they don't have attestedCredentialData.
                attestedCredentialData = null;
            }
        } else {
            attestedCredentialData = null;
        }
        if (AuthenticatorData.checkFlagED(flags)) {
            extensions = convertToExtensions(byteBuffer);
        } else {
            extensions = new AuthenticationExtensionsAuthenticatorOutputs<>();
        }
        if (byteBuffer.hasRemaining()) {
            throw new DataConversionException("provided data does not have proper byte layout");
        }
        return new AuthenticatorData<>(rpIdHash, flags, counter, attestedCredentialData, extensions);
    } catch (IllegalArgumentException e) {
        throw new DataConversionException(e);
    } catch (BufferUnderflowException e) {
        throw new DataConversionException("provided data does not have proper byte layout", e);
    }
}
Also used : AttestedCredentialData(com.webauthn4j.data.attestation.authenticator.AttestedCredentialData) AuthenticatorData(com.webauthn4j.data.attestation.authenticator.AuthenticatorData) DataConversionException(com.webauthn4j.converter.exception.DataConversionException) ByteBuffer(java.nio.ByteBuffer) BufferUnderflowException(java.nio.BufferUnderflowException) NonNull(org.checkerframework.checker.nullness.qual.NonNull)

Example 5 with AuthenticatorData

use of com.webauthn4j.data.attestation.authenticator.AuthenticatorData in project webauthn4j by webauthn4j.

the class AuthenticatorDataConverterTest method serialize_deserialize_test.

@Test
void serialize_deserialize_test() {
    // Given
    byte[] rpIdHash = new byte[32];
    // noinspection UnnecessaryLocalVariable
    byte flags = BIT_ED;
    AuthenticationExtensionsAuthenticatorOutputs.BuilderForRegistration builder = new AuthenticationExtensionsAuthenticatorOutputs.BuilderForRegistration();
    builder.setUvm(new UvmEntries());
    AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authenticatorData = new AuthenticatorData<>(rpIdHash, flags, 0, builder.build());
    // When
    byte[] serialized = new AuthenticatorDataConverter(objectConverter).convert(authenticatorData);
    AuthenticatorData<RegistrationExtensionAuthenticatorOutput> result = new AuthenticatorDataConverter(objectConverter).convert(serialized);
    // Then
    assertThat(result.getRpIdHash()).isNotNull();
    assertThat(result.getRpIdHash()).hasSize(32);
    assertThat(result.getFlags()).isEqualTo(BIT_ED);
    assertThat(result.getSignCount()).isZero();
    assertThat(result.getAttestedCredentialData()).isNull();
    assertThat(result.getExtensions().getKeys()).contains(UserVerificationMethodExtensionAuthenticatorOutput.ID);
}
Also used : AuthenticationExtensionsAuthenticatorOutputs(com.webauthn4j.data.extension.authenticator.AuthenticationExtensionsAuthenticatorOutputs) AuthenticatorData(com.webauthn4j.data.attestation.authenticator.AuthenticatorData) RegistrationExtensionAuthenticatorOutput(com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput) UvmEntries(com.webauthn4j.data.extension.UvmEntries) Test(org.junit.jupiter.api.Test)

Aggregations

AuthenticatorData (com.webauthn4j.data.attestation.authenticator.AuthenticatorData)6 PublicKeyCredentialDescriptor (com.webauthn4j.data.PublicKeyCredentialDescriptor)3 AttestedCredentialData (com.webauthn4j.data.attestation.authenticator.AttestedCredentialData)3 RegistrationExtensionAuthenticatorOutput (com.webauthn4j.data.extension.authenticator.RegistrationExtensionAuthenticatorOutput)3 AttestationObject (com.webauthn4j.data.attestation.AttestationObject)2 AttestationStatement (com.webauthn4j.data.attestation.statement.AttestationStatement)2 AuthenticationExtensionAuthenticatorOutput (com.webauthn4j.data.extension.authenticator.AuthenticationExtensionAuthenticatorOutput)2 AuthenticationExtensionsAuthenticatorOutputs (com.webauthn4j.data.extension.authenticator.AuthenticationExtensionsAuthenticatorOutputs)2 DataConversionException (com.webauthn4j.converter.exception.DataConversionException)1 PublicKeyCredentialParameters (com.webauthn4j.data.PublicKeyCredentialParameters)1 PublicKeyCredentialRpEntity (com.webauthn4j.data.PublicKeyCredentialRpEntity)1 AAGUID (com.webauthn4j.data.attestation.authenticator.AAGUID)1 COSEKey (com.webauthn4j.data.attestation.authenticator.COSEKey)1 EC2COSEKey (com.webauthn4j.data.attestation.authenticator.EC2COSEKey)1 AttestationCertificatePath (com.webauthn4j.data.attestation.statement.AttestationCertificatePath)1 FIDOU2FAttestationStatement (com.webauthn4j.data.attestation.statement.FIDOU2FAttestationStatement)1 UvmEntries (com.webauthn4j.data.extension.UvmEntries)1 CredentialCreationResponse (com.webauthn4j.test.authenticator.CredentialCreationResponse)1 CredentialRequestResponse (com.webauthn4j.test.authenticator.CredentialRequestResponse)1 BufferUnderflowException (java.nio.BufferUnderflowException)1