diff --git a/NEWS b/NEWS index f20ac12be..1a38fc2b5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,12 @@ +== Version 1.12.0 (unreleased) == + +New features: + +* New method `RegisteredCredential.builder().publicKeyEs256Raw(ByteArray)`. This + is a mutually exclusive alternative to `.publicKeyCose(ByteArray)`, for easier + backwards-compatibility with U2F-formatted (Raw ANSI X9.62) public keys. + + == Version 1.11.0 == Deprecated features: diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java index 0c2c787fd..ef1cfb89f 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegisteredCredential.java @@ -30,6 +30,7 @@ import com.yubico.webauthn.data.AuthenticatorAssertionResponse; import com.yubico.webauthn.data.AuthenticatorData; import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.COSEAlgorithmIdentifier; import com.yubico.webauthn.data.PublicKeyCredentialDescriptor; import com.yubico.webauthn.data.UserIdentity; import lombok.Builder; @@ -143,12 +144,85 @@ public class Step3 { * {@link RegisteredCredentialBuilder#publicKeyCose(ByteArray) publicKeyCose} is a required * parameter. * + *
Alternatively, the public key can be specified using the {@link
+ * #publicKeyEs256Raw(ByteArray)} method if the key is stored in the U2F format (
+ * ALG_KEY_ECC_X962_RAW
as specified in FIDO
+ * Registry §3.6.2 Public Key Representation Formats). This is mostly useful for public
+ * keys registered via the U2F JavaScript API.
+ *
+ * @see #publicKeyEs256Raw(ByteArray)
* @see RegisteredCredentialBuilder#publicKeyCose(ByteArray)
+ * @see FIDO
+ * Registry §3.6.2 Public Key Representation Formats
*/
public RegisteredCredentialBuilder publicKeyCose(ByteArray publicKeyCose) {
return builder.publicKeyCose(publicKeyCose);
}
+
+ /**
+ * Specify the credential public key in U2F format.
+ *
+ *
An alternative to {@link #publicKeyCose(ByteArray)}, this method expects an {@link
+ * COSEAlgorithmIdentifier#ES256 ES256} public key in ALG_KEY_ECC_X962_RAW
+ * format as specified in FIDO
+ * Registry §3.6.2 Public Key Representation Formats.
+ *
+ *
This is primarily intended for public keys registered via the U2F JavaScript API. If
+ * your application has only used the navigator.credentials.create()
API to
+ * register credentials, you should use {@link #publicKeyCose(ByteArray)} instead.
+ *
+ * @see RegisteredCredentialBuilder#publicKeyCose(ByteArray)
+ */
+ public RegisteredCredentialBuilder publicKeyEs256Raw(ByteArray publicKeyEs256Raw) {
+ return builder.publicKeyCose(WebAuthnCodecs.rawEcKeyToCose(publicKeyEs256Raw));
+ }
}
}
+
+ /**
+ * The credential public key encoded in COSE_Key format, as defined in Section 7 of RFC 8152. This method overwrites {@link
+ * #publicKeyEs256Raw(ByteArray)}.
+ *
+ *
This is used to verify the {@link AuthenticatorAssertionResponse#getSignature() signature} + * in authentication assertions. + * + *
Alternatively, the public key can be specified using the {@link
+ * #publicKeyEs256Raw(ByteArray)} method if the key is stored in the U2F format (
+ * ALG_KEY_ECC_X962_RAW
as specified in FIDO
+ * Registry §3.6.2 Public Key Representation Formats). This is mostly useful for public keys
+ * registered via the U2F JavaScript API.
+ *
+ * @see AttestedCredentialData#getCredentialPublicKey()
+ * @see RegistrationResult#getPublicKeyCose()
+ */
+ public RegisteredCredentialBuilder publicKeyCose(@NonNull ByteArray publicKeyCose) {
+ this.publicKeyCose = publicKeyCose;
+ return this;
+ }
+
+ /**
+ * Specify the credential public key in U2F format. This method overwrites {@link
+ * #publicKeyCose(ByteArray)}.
+ *
+ *
An alternative to {@link #publicKeyCose(ByteArray)}, this method expects an {@link
+ * COSEAlgorithmIdentifier#ES256 ES256} public key in ALG_KEY_ECC_X962_RAW
format
+ * as specified in FIDO
+ * Registry §3.6.2 Public Key Representation Formats.
+ *
+ *
This is primarily intended for public keys registered via the U2F JavaScript API. If your
+ * application has only used the navigator.credentials.create()
API to register
+ * credentials, you should use {@link #publicKeyCose(ByteArray)} instead.
+ *
+ * @see RegisteredCredentialBuilder#publicKeyCose(ByteArray)
+ */
+ public RegisteredCredentialBuilder publicKeyEs256Raw(ByteArray publicKeyEs256Raw) {
+ return publicKeyCose(WebAuthnCodecs.rawEcKeyToCose(publicKeyEs256Raw));
+ }
}
}
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
index 6a784759a..986061eb2 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java
@@ -40,6 +40,8 @@
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
final class WebAuthnCodecs {
@@ -63,6 +65,28 @@ static ByteArray ecPublicKeyToRaw(ECPublicKey key) {
Bytes.concat(yPadding, Arrays.copyOfRange(y, Math.max(0, y.length - 32), y.length))));
}
+ static ByteArray rawEcKeyToCose(ByteArray key) {
+ final byte[] keyBytes = key.getBytes();
+ if (!(keyBytes.length == 64 || (keyBytes.length == 65 && keyBytes[0] == 0x04))) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Raw key must be 64 bytes long or be 65 bytes long and start with 0x04, was %d bytes starting with %02x",
+ keyBytes.length, keyBytes[0]));
+ }
+ final int start = (keyBytes.length == 64) ? 0 : 1;
+
+ final Map