use of com.webauthn4j.data.PublicKeyCredentialParameters in project OpenUnison by TremoloSecurity.
the class WebAuthnRegistration method doFilter.
@Override
public void doFilter(HttpFilterRequest request, HttpFilterResponse response, HttpFilterChain chain) throws Exception {
request.getServletRequest().setAttribute("com.tremolosecurity.unison.proxy.noRedirectOnError", "com.tremolosecurity.unison.proxy.noRedirectOnError");
if (request.getMethod().equalsIgnoreCase("GET")) {
if (request.getRequestURI().endsWith("/credentialCreateOptions")) {
ObjectConverter oc = new ObjectConverter();
String rpId = getRpId(request.getServletRequest());
AuthInfo userData = ((AuthController) request.getSession().getAttribute(ProxyConstants.AUTH_CTL)).getAuthInfo();
WebAuthnUserData webAuthnUserData = WebAuthnUtils.lookupWebAuthnUserData(userData, challengeStoreAttribute, encryptionKeyName);
if (webAuthnUserData == null) {
// no data yet, let's create
webAuthnUserData = new WebAuthnUserData(userData.getAttribs().get(this.uidAttributeName).getValues().get(0));
WebAuthnUtils.storeWebAuthnUserData(webAuthnUserData, this.encryptionKeyName, userData, this.workflowName, this.uidAttributeName, this.challengeStoreAttribute);
}
Challenge challenge = new DefaultChallenge();
CborConverter cbor = oc.getCborConverter();
String b64UrlChallenge = Base64UrlUtil.encodeToString(challenge.getValue());
AuthenticatorSelectionCriteria authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(authenticatorAttachment, requireResisentKey, userVerificationRequirement);
PublicKeyCredentialParameters publicKeyCredentialParameters = new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256);
String b64UrlId = Base64.getUrlEncoder().encodeToString(webAuthnUserData.getId());
ServerProperty serverProperty = new ServerProperty(new Origin(request.getRequestURL().toString()), rpId, challenge, webAuthnUserData.getId());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
byte[] yourBytes = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(serverProperty);
out.flush();
yourBytes = bos.toByteArray();
} finally {
try {
bos.close();
} catch (IOException ex) {
// ignore close exception
}
}
request.getSession().setAttribute("tremolo.io/webauthn/serverProperty", serverProperty);
PublicKeyCredentialUserEntity publicKeyCredentialUserEntity = new PublicKeyCredentialUserEntity(webAuthnUserData.getId(), webAuthnUserData.getDisplayName(), webAuthnUserData.getDisplayName());
AuthenticationExtensionsClientInputs<RegistrationExtensionClientInput> extensions = new AuthenticationExtensionsClientInputs<>();
PublicKeyCredentialCreationOptions credentialCreationOptions = new PublicKeyCredentialCreationOptions(new PublicKeyCredentialRpEntity(rpId, rpId), publicKeyCredentialUserEntity, challenge, Collections.singletonList(publicKeyCredentialParameters), null, Collections.emptyList(), authenticatorSelectionCriteria, AttestationConveyancePreference.NONE, extensions);
ObjectMapper mapper = new ObjectMapper();
// mapper.writeValueAsString(credentialCreationOptions);
String publecCredentialCreationOptionsJson = oc.getJsonConverter().writeValueAsString(credentialCreationOptions);
JSONObject root = (JSONObject) new JSONParser().parse(publecCredentialCreationOptionsJson);
root.put("challenge", b64UrlChallenge);
((JSONObject) root.get("user")).put("id", b64UrlId);
JSONObject publicKeyRoot = new JSONObject();
publicKeyRoot.put("publicKey", root);
publicKeyRoot.put("serverProperty", Base64.getUrlEncoder().encodeToString(yourBytes));
response.getWriter().println(publicKeyRoot.toString());
} else {
StringBuilder createCredentialURL = new StringBuilder(request.getRequestURL().toString());
createCredentialURL.append("/credentialCreateOptions");
request.setAttribute("tremolo.io/webauthn/challengeurl", createCredentialURL.toString());
createCredentialURL = new StringBuilder(request.getRequestURL().toString());
createCredentialURL.append("/finishregistration");
request.setAttribute("tremolo.io/webauthn/finishregistration", createCredentialURL.toString());
request.getRequestDispatcher(this.challengeURI).forward(request.getServletRequest(), response.getServletResponse());
}
} else if (request.getMethod().equalsIgnoreCase("POST")) {
try {
storeCredential(request);
} catch (WebAuthnException e) {
JSONObject resp = new JSONObject();
resp.put("error", e.getMessage());
response.sendError(500);
response.getWriter().println(resp.toString());
} catch (Throwable t) {
JSONObject resp = new JSONObject();
logger.error("Could not store credential", t);
resp.put("error", "There was an error, please contanct your system administrator");
response.sendError(500);
response.getWriter().println(resp.toString());
}
}
}
use of com.webauthn4j.data.PublicKeyCredentialParameters in project webauthn4j by webauthn4j.
the class RegistrationDataValidatorTest method validateAlg_test.
@Test
void validateAlg_test() {
List<PublicKeyCredentialParameters> pubKeyCredParams = Arrays.asList(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256), new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS256));
target.validateAlg(COSEAlgorithmIdentifier.ES256, pubKeyCredParams);
}
use of com.webauthn4j.data.PublicKeyCredentialParameters 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;
}
use of com.webauthn4j.data.PublicKeyCredentialParameters in project webauthn4j by webauthn4j.
the class CoreRegistrationDataValidator method validate.
/**
* It is up to caller responsibility to inject challenge into clientData and validate it equals to challenge stored in server side
*
* @param registrationData registration data
* @param registrationParameters registration parameters
*/
// as null check is done by BeanAssertUtil#validate
@SuppressWarnings("ConstantConditions")
public void validate(@NonNull CoreRegistrationData registrationData, @NonNull CoreRegistrationParameters registrationParameters) {
// spec| Step1
// spec| Let options be a new PublicKeyCredentialCreationOptions structure configured to the Relying Party's needs for the ceremony.
// (This step is done on client slide and out of WebAuthn4J responsibility.)
// spec| Step2
// spec| Call navigator.credentials.create() and pass options as the publicKey option. Let credential be the result of the successfully resolved promise.
// spec| If the promise is rejected, abort the ceremony with a user-visible error,
// spec| or otherwise guide the user experience as might be determinable from the context available in the rejected promise.
// spec| For example if the promise is rejected with an error code equivalent to "InvalidStateError",
// spec| the user might be instructed to use a different authenticator.
// spec| For information on different error contexts and the circumstances leading to them, see § 6.3.2 The authenticatorMakeCredential Operation.
// (This step is done on client slide and out of WebAuthn4J responsibility.)
// spec| Step3
// spec| Let response be credential.response. If response is not an instance of AuthenticatorAttestationResponse, abort the ceremony with a user-visible error.
// (This step is done on client slide and out of WebAuthn4J responsibility.)
// spec| Step4
// spec| Let clientExtensionResults be the result of calling credential.getClientExtensionResults().
// (This step is only applicable to WebAuthn)
// spec| Step5
// spec| Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON.
// (This step is only applicable to WebAuthn)
BeanAssertUtil.validate(registrationData);
AssertUtil.notNull(registrationParameters, "registrationParameters must not be null");
// spec| Step6
// spec| Let C, the client data claimed as collected during the credential creation,
// spec| be the result of running an implementation-specific JSON parser on JSONtext.
// (This step is only applicable to WebAuthn)
AttestationObject attestationObject = registrationData.getAttestationObject();
validateAuthenticatorDataField(attestationObject.getAuthenticatorData());
CoreServerProperty serverProperty = registrationParameters.getServerProperty();
CoreRegistrationObject registrationObject = createCoreRegistrationObject(registrationData, registrationParameters);
AuthenticatorData<RegistrationExtensionAuthenticatorOutput> authenticatorData = attestationObject.getAuthenticatorData();
COSEKey coseKey = authenticatorData.getAttestedCredentialData().getCOSEKey();
validateCOSEKey(coseKey);
// spec| Step7
// spec| Verify that the value of C.type is webauthn.create.
// (This step is only applicable to WebAuthn)
// spec| Step8
// spec| Verify that the value of C.challenge equals the base64url encoding of options.challenge.
// (This step is only applicable to WebAuthn)
// spec| Step9
// spec| Verify that the value of C.origin matches the Relying Party's origin.
// (This step is only applicable to WebAuthn)
// spec| Step10
// spec| Verify that the value of C.tokenBinding.status matches the state of Token Binding for the TLS connection over
// spec| which the assertion was obtained. If Token Binding was used on that TLS connection, also verify that
// spec| C.tokenBinding.id matches the base64url encoding of the Token Binding ID for the connection.
// (This step is only applicable to WebAuthn)
// spec| Step11
// spec| Let hash be the result of computing a hash over response.clientDataJSON using SHA-256.
// spec| Step12
// spec| Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse structure to
// spec| obtain the attestation statement format fmt, the authenticator data authData, and the attestation statement attStmt.
// (This step is done on caller.)
// spec| Step13
// spec| Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party.
rpIdHashValidator.validate(authenticatorData.getRpIdHash(), serverProperty);
// spec| Step14, 15
// spec| Verify that the User Present bit of the flags in authData is set.
// spec| If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set.
validateUVUPFlags(authenticatorData, registrationParameters.isUserVerificationRequired(), registrationParameters.isUserPresenceRequired());
// spec| Step16
// spec| Verify that the "alg" parameter in the credential public key in authData matches the alg attribute of one of the items in options.pubKeyCredParams.
COSEAlgorithmIdentifier alg = authenticatorData.getAttestedCredentialData().getCOSEKey().getAlgorithm();
List<PublicKeyCredentialParameters> pubKeyCredParams = registrationParameters.getPubKeyCredParams();
validateAlg(alg, pubKeyCredParams);
// spec| Step17
// spec| Verify that the values of the client extension outputs in clientExtensionResults and the authenticator extension outputs in the extensions in authData are as expected,
// spec| considering the client extension input values that were given in options.extensions and any specific policy of the Relying Party regarding unsolicited extensions,
// spec| i.e., those that were not specified as part of options.extensions.
// spec| In the general case, the meaning of "are as expected" is specific to the Relying Party and which extensions are in use.
AuthenticationExtensionsAuthenticatorOutputs<RegistrationExtensionAuthenticatorOutput> authenticationExtensionsAuthenticatorOutputs = authenticatorData.getExtensions();
authenticatorExtensionValidator.validate(authenticationExtensionsAuthenticatorOutputs);
// spec| Step18-21
attestationValidator.validate(registrationObject);
// validate with custom logic
for (CustomCoreRegistrationValidator customRegistrationValidator : customRegistrationValidators) {
customRegistrationValidator.validate(registrationObject);
}
}
use of com.webauthn4j.data.PublicKeyCredentialParameters in project webauthn4j-spring-security by webauthn4j.
the class WebSecurityConfig method configure.
/**
* Configure SecurityFilterChain
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// WebAuthn Config
http.apply(WebAuthnLoginConfigurer.webAuthnLogin()).attestationOptionsEndpoint().rp().name("WebAuthn4J Spring Security Sample").and().pubKeyCredParams(new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.ES256), new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, COSEAlgorithmIdentifier.RS1)).extensions().entry("example.extension", "test").and().assertionOptionsEndpoint().extensions().entry("example.extension", "test").and();
FidoServerAttestationOptionsEndpointFilter fidoServerAttestationOptionsEndpointFilter = new FidoServerAttestationOptionsEndpointFilter(objectConverter, attestationOptionsProvider, challengeRepository);
FidoServerAttestationResultEndpointFilter fidoServerAttestationResultEndpointFilter = new FidoServerAttestationResultEndpointFilter(objectConverter, userDetailsManager, webAuthnAuthenticatorManager, webAuthnRegistrationRequestValidator);
fidoServerAttestationResultEndpointFilter.setUsernameNotFoundHandler(new SampleUsernameNotFoundHandler(userDetailsManager));
FidoServerAssertionOptionsEndpointFilter fidoServerAssertionOptionsEndpointFilter = new FidoServerAssertionOptionsEndpointFilter(objectConverter, assertionOptionsProvider, challengeRepository);
FidoServerAssertionResultEndpointFilter fidoServerAssertionResultEndpointFilter = new FidoServerAssertionResultEndpointFilter(objectConverter, serverPropertyProvider);
fidoServerAssertionResultEndpointFilter.setAuthenticationManager(authenticationManagerBean());
http.addFilterAfter(fidoServerAttestationOptionsEndpointFilter, SessionManagementFilter.class);
http.addFilterAfter(fidoServerAttestationResultEndpointFilter, SessionManagementFilter.class);
http.addFilterAfter(fidoServerAssertionOptionsEndpointFilter, SessionManagementFilter.class);
http.addFilterAfter(fidoServerAssertionResultEndpointFilter, SessionManagementFilter.class);
// Authorization
http.authorizeRequests().mvcMatchers("/").permitAll().mvcMatchers("/api/auth/status").permitAll().mvcMatchers(HttpMethod.GET, "/login").permitAll().mvcMatchers(HttpMethod.POST, "/api/profile").permitAll().mvcMatchers("/health/**").permitAll().mvcMatchers("/info/**").permitAll().mvcMatchers("/h2-console/**").denyAll().mvcMatchers("/api/admin/**").hasRole(ADMIN_ROLE).anyRequest().fullyAuthenticated();
// TODO:
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.csrf().ignoringAntMatchers("/webauthn/**");
}
Aggregations