Skip to content

Commit

Permalink
Release 1.6.3
Browse files Browse the repository at this point in the history
webauthn-server-attestation:

- Added new YubiKey AAGUIDs to metadata.json

webauthn-server-core:

- Bumped Jackson dependency to version 2.11.0 in response to CVEs:
  - CVE-2020-9546
  - CVE-2020-10672
  - CVE-2020-10969
  - CVE-2020-11620
- Fixed incorrect JavaDoc on AssertionResult.isSignatureCounterValid():
  it will also return true if both counters are zero.
  • Loading branch information
emlun committed May 25, 2020
2 parents bc94105 + 929d26d commit 74ff8c6
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 63 deletions.
19 changes: 10 additions & 9 deletions .github/workflows/scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
env:
SCAN_IMG:
yes-docker-local.artifactory.in.yubico.org/static-code-analysis/java:v1
SECRET: ${{ secrets.ARTIFACTORY_READER_TOKEN }}

jobs:
build:
Expand All @@ -17,17 +18,17 @@ jobs:
steps:
- uses: actions/checkout@master

- name: Prep scan
run: |
docker login yes-docker-local.artifactory.in.yubico.org/ \
-u svc-static-code-analysis-reader \
-p ${{ secrets.ARTIFACTORY_READER_TOKEN }}
docker pull ${SCAN_IMG}
- name: Scan and fail on warnings
run: |
docker run -v${PWD}:/k \
-e PROJECT_NAME=${GITHUB_REPOSITORY#Yubico/} -t ${SCAN_IMG}
if [ "${SECRET}" != "" ]; then
docker login yes-docker-local.artifactory.in.yubico.org/ \
-u svc-static-code-analysis-reader -p ${SECRET}
docker pull ${SCAN_IMG}
docker run -v${PWD}:/k \
-e PROJECT_NAME=${GITHUB_REPOSITORY#Yubico/} -t ${SCAN_IMG}
else
echo "No docker registry credentials, not scanning"
fi
- uses: actions/upload-artifact@master
if: failure()
Expand Down
18 changes: 18 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
== Version 1.6.3 ==

webauthn-server-attestation:

- Added new YubiKey AAGUIDs to metadata.json


webauthn-server-core:

- Bumped Jackson dependency to version 2.11.0 in response to CVEs:
- CVE-2020-9546
- CVE-2020-10672
- CVE-2020-10969
- CVE-2020-11620
- Fixed incorrect JavaDoc on AssertionResult.isSignatureCounterValid(): it will
also return true if both counters are zero.


== Version 1.6.2 ==

- Fixed dependencies missing from release POM metadata
Expand Down
14 changes: 0 additions & 14 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,6 @@ and other higher level concepts can make use of this authentication mechanism,
but the authentication mechanism alone does not make a security system.


== Known issues

- In the link:webauthn-server-demo[example app], authentication does not work in
Chrome as of version 70. This is due to a
link:https://bugs.chromium.org/p/chromium/issues/detail?id=847878[bug in
Chrome] which will not be worked around here. To work around this in
application code, you can omit the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/latest/com/yubico/webauthn/data/AuthenticatorAssertionResponse.AuthenticatorAssertionResponseBuilder.html#userHandle-java.util.Optional[`userHandle`]
when constructing an
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/latest/com/yubico/webauthn/data/AuthenticatorAssertionResponse.html[`AuthenticatorAssertionResponse`]
value if the `userHandle` is empty. See
https://github.com/Yubico/java-webauthn-server/issues/12 .


== Documentation

See the
Expand Down
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ allprojects {
Map<String, String> dependencyVersions = [
'ch.qos.logback:logback-classic:1.2.3',
'com.augustcellars.cose:cose-java:1.0.0',
'com.fasterxml.jackson.core:jackson-databind:2.9.10.3',
'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.9.10',
'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.10',
'com.fasterxml.jackson.core:jackson-databind:2.11.0',
'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.11.0',
'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.11.0',
'com.google.guava:guava:19.0',
'com.upokecenter:cbor:4.0.1',
'javax.activation:activation:1.1.1',
Expand Down
4 changes: 4 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Required for publishing to Sonatype Nexus
# See https://issues.sonatype.org/browse/OSSRH-54054
# See https://docs.gradle.org/6.0.1/release-notes.html#publication-of-sha256-and-sha512-checksums
systemProp.org.gradle.internal.publish.checksums.insecure=true
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ public final class MetadataObject {

private static final TypeReference<Map<String, String>> MAP_STRING_STRING_TYPE = new TypeReference<Map<String, String>>() {
};
private static final TypeReference LIST_STRING_TYPE = new TypeReference<List<String>>() {
private static final TypeReference<List<String>> LIST_STRING_TYPE = new TypeReference<List<String>>() {
};
private static final TypeReference LIST_JSONNODE_TYPE = new TypeReference<List<JsonNode>>() {
private static final TypeReference<List<JsonNode>> LIST_JSONNODE_TYPE = new TypeReference<List<JsonNode>>() {
};

private final transient JsonNode data;
Expand Down
42 changes: 41 additions & 1 deletion webauthn-server-attestation/src/main/resources/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe",
"version": 11,
"version": 12,
"vendorInfo": {
"url": "https://yubico.com",
"imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png",
Expand All @@ -26,6 +26,16 @@
"value": "6d44ba9bf6ec2e49b9300c8fe920cb73"
}
}
},
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "149a20218ef6413396b881f8d5b7f1f5"
}
}
}
]
},
Expand All @@ -49,6 +59,16 @@
"value": "1.3.6.1.4.1.41482.1.1",
"key": "1.3.6.1.4.1.41482.2"
}
},
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "b92c3f9ac0144056887f140a2501163b"
}
}
}
]
},
Expand Down Expand Up @@ -144,6 +164,16 @@
"value": "fa2b99dc9e3942578f924a30d23c4118"
}
}
},
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "2fc0579f811347eab116bb5a8db9202a"
}
}
}
]
},
Expand All @@ -163,6 +193,16 @@
"value": "cb69481e8ff7403993ec0a2729a154a8"
}
}
},
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "ee882879721c491397753dfcce97072a"
}
}
}
]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,13 @@ public class AssertionResult {
private final long signatureCount;

/**
* <code>true</code> if and only if the {@link AuthenticatorData#getSignatureCounter() signature counter value}
* in the assertion was strictly greater than {@link RegisteredCredential#getSignatureCount() the stored one}.
* <code>true</code> if and only if at least one of the following is true:
* <ul>
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the assertion was strictly
* greater than {@link RegisteredCredential#getSignatureCount() the stored one}.</li>
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the assertion and
* {@link RegisteredCredential#getSignatureCount() the stored one} were both zero.</li>
* </ul>
*
* @see <a href="https://www.w3.org/TR/2019/PR-webauthn-20190117/#sec-authenticator-data">§6.1. Authenticator
* Data</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object JacksonGenerators {
} yield {
val o = jsonFactory.objectNode()
for { (name, value) <- names.zip(values) } {
o.set(name, value)
o.set[ObjectNode](name, value)
}
o
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ object RegistrationTestData {
)

object AndroidKey {
val BasicAttestation: RegistrationTestData = Packed.SelfAttestation.editAttestationObject("fmt", "android-key")
val BasicAttestation: RegistrationTestData = Packed.SelfAttestation.setAttestationStatementFormat("android-key")
}
object AndroidSafetynet {
val RealExample: RegistrationTestData = new RegistrationTestData(
Expand Down Expand Up @@ -254,7 +254,7 @@ object RegistrationTestData {
) { override def regenerate() = TestAuthenticator.createSelfAttestedCredential(AttestationMaker.packed(_), keyAlgorithm = COSEAlgorithmIdentifier.RS1) }
}
object Tpm {
val PrivacyCa: RegistrationTestData = Packed.BasicAttestation.editAttestationObject("fmt", "tpm")
val PrivacyCa: RegistrationTestData = Packed.BasicAttestation.setAttestationStatementFormat("tpm")
}
}

Expand Down Expand Up @@ -309,14 +309,14 @@ case class RegistrationTestData(
.map(x5c => x5c.elements().asScala.toList.last)
.map(node => CertificateParser.parseDer(node.binaryValue()))

def editClientData[A <: JsonNode](updater: ObjectNode => A): RegistrationTestData = copy(
def editClientData(updater: ObjectNode => JsonNode): RegistrationTestData = copy(
clientDataJson = JacksonCodecs.json.writeValueAsString(
updater(JacksonCodecs.json.readTree(clientDataJson).asInstanceOf[ObjectNode])
)
)

def editClientData[A <: JsonNode](name: String, value: A): RegistrationTestData = editClientData { clientData: ObjectNode =>
clientData.set(name, value)
def editClientData(name: String, value: JsonNode): RegistrationTestData = editClientData { clientData: ObjectNode =>
clientData.set[ObjectNode](name, value)
}
def editClientData(name: String, value: String): RegistrationTestData = editClientData(name, RegistrationTestData.jsonFactory.textNode(value))
def responseChallenge: ByteArray = clientData.getChallenge
Expand All @@ -327,13 +327,13 @@ case class RegistrationTestData(
RegistrationTestData.jsonFactory.textNode(value.getBase64Url)
)

def editAttestationObject[A <: JsonNode](name: String, value: A): RegistrationTestData = copy(
def editAttestationObject(name: String, value: JsonNode): RegistrationTestData = copy(
attestationObject = new ByteArray(JacksonCodecs.cbor.writeValueAsBytes(
JacksonCodecs.cbor.readTree(attestationObject.getBytes).asInstanceOf[ObjectNode]
.set(name, value)
))
)
def editAttestationObject[A <: JsonNode](name: String, updater: JsonNode => A): RegistrationTestData = {
def updateAttestationObject(name: String, updater: JsonNode => JsonNode): RegistrationTestData = {
val attObj = JacksonCodecs.cbor.readTree(attestationObject.getBytes)
copy(
attestationObject = new ByteArray(JacksonCodecs.cbor.writeValueAsBytes(
Expand All @@ -344,8 +344,8 @@ case class RegistrationTestData(
)
}

def editAttestationObject(name: String, value: String): RegistrationTestData =
editAttestationObject(name, RegistrationTestData.jsonFactory.textNode(value))
def setAttestationStatementFormat(value: String): RegistrationTestData =
editAttestationObject("fmt", RegistrationTestData.jsonFactory.textNode(value))

def editAuthenticatorData(updater: ByteArray => ByteArray): RegistrationTestData = {
val attObj: ObjectNode = JacksonCodecs.cbor.readTree(attestationObject.getBytes).asInstanceOf[ObjectNode]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck

it("Verification succeeds if client data specifies token binding is unsupported, and RP does not use it.") {
val steps = finishRegistration(testData = RegistrationTestData.FidoU2f.BasicAttestation
.editClientData(_.without("tokenBinding"))
.editClientData(_.without[ObjectNode]("tokenBinding"))
)
val step: FinishRegistrationSteps#Step6 = steps.begin.next.next.next.next.next

Expand All @@ -435,7 +435,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
it("Verification fails if client data does not specify token binding status and RP specifies token binding ID.") {
val steps = finishRegistration(
callerTokenBindingId = Some(ByteArray.fromBase64Url("YELLOWSUBMARINE")),
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without("tokenBinding"))
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without[ObjectNode]("tokenBinding"))
)
val step: FinishRegistrationSteps#Step6 = steps.begin.next.next.next.next.next

Expand All @@ -447,7 +447,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
it("Verification succeeds if client data does not specify token binding status and RP does not specify token binding ID.") {
val steps = finishRegistration(
callerTokenBindingId = None,
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without("tokenBinding"))
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without[ObjectNode]("tokenBinding"))
)
val step: FinishRegistrationSteps#Step6 = steps.begin.next.next.next.next.next

Expand Down Expand Up @@ -493,7 +493,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
it("Verification fails if RP specifies token binding ID but client does not support it.") {
val steps = finishRegistration(
callerTokenBindingId = Some(ByteArray.fromBase64Url("YELLOWSUBMARINE")),
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without("tokenBinding"))
testData = RegistrationTestData.FidoU2f.BasicAttestation.editClientData(_.without[ObjectNode]("tokenBinding"))
)
val step: FinishRegistrationSteps#Step6 = steps.begin.next.next.next.next.next

Expand Down Expand Up @@ -749,7 +749,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
describe("13. Determine the attestation statement format by performing a USASCII case-sensitive match on fmt against the set of supported WebAuthn Attestation Statement Format Identifier values. An up-to-date list of registered WebAuthn Attestation Statement Format Identifier values is maintained in the IANA registry of the same name [WebAuthn-Registries].") {
def setup(format: String): FinishRegistrationSteps = {
finishRegistration(
testData = RegistrationTestData.FidoU2f.BasicAttestation.editAttestationObject("fmt", format)
testData = RegistrationTestData.FidoU2f.BasicAttestation.setAttestationStatementFormat(format)
)
}

Expand Down Expand Up @@ -794,9 +794,9 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck

describe("If allowUntrustedAttestation is set,") {
it("a fido-u2f attestation is still rejected if invalid.") {
val testData = RegistrationTestData.FidoU2f.BasicAttestation.editAttestationObject("attStmt", { attStmtNode: JsonNode =>
val testData = RegistrationTestData.FidoU2f.BasicAttestation.updateAttestationObject("attStmt", { attStmtNode: JsonNode =>
attStmtNode.asInstanceOf[ObjectNode]
.set("sig", jsonFactory.binaryNode(Array(0, 0, 0, 0)))
.set[ObjectNode]("sig", jsonFactory.binaryNode(Array(0, 0, 0, 0)))
})
val steps = finishRegistration(
testData = testData,
Expand Down Expand Up @@ -1457,33 +1457,33 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
describe("1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform CBOR decoding on it to extract the contained fields.") {
it("Fails if attStmt.ver is a number value.") {
val testData = defaultTestData
.editAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set("ver", jsonFactory.numberNode(123)))
.updateAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set[ObjectNode]("ver", jsonFactory.numberNode(123)))
checkFails(testData)
}

it("Fails if attStmt.ver is missing.") {
val testData = defaultTestData
.editAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].without("ver"))
.updateAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].without[ObjectNode]("ver"))
checkFails(testData)
}

it("Fails if attStmt.response is a text value.") {
val testData = defaultTestData
.editAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set("response", jsonFactory.textNode(new ByteArray(attStmt.get("response").binaryValue()).getBase64Url)))
.updateAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set[ObjectNode]("response", jsonFactory.textNode(new ByteArray(attStmt.get("response").binaryValue()).getBase64Url)))
checkFails(testData)
}

it("Fails if attStmt.response is missing.") {
val testData = defaultTestData
.editAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].without("response"))
.updateAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].without[ObjectNode]("response"))
checkFails(testData)
}
}

describe("2. Verify that response is a valid SafetyNet response of version ver.") {
it("Fails if there's a difference in the signature.") {
val testData = defaultTestData
.editAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set("response", jsonFactory.binaryNode(editByte(new ByteArray(attStmt.get("response").binaryValue()), 2000, b => ((b + 1) % 26 + 0x41).toByte).getBytes)))
.updateAttestationObject("attStmt", attStmt => attStmt.asInstanceOf[ObjectNode].set[ObjectNode]("response", jsonFactory.binaryNode(editByte(new ByteArray(attStmt.get("response").binaryValue()), 2000, b => ((b + 1) % 26 + 0x41).toByte).getBytes)))

val result: Try[Boolean] = Try(verifier.verifyAttestationSignature(
new AttestationObject(testData.attestationObject),
Expand Down Expand Up @@ -1555,7 +1555,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
}

it("Unknown attestation statement formats fail.") {
val steps = finishRegistration(testData = RegistrationTestData.FidoU2f.BasicAttestation.editAttestationObject("fmt", "urgel"))
val steps = finishRegistration(testData = RegistrationTestData.FidoU2f.BasicAttestation.setAttestationStatementFormat("urgel"))
val step: FinishRegistrationSteps#Step14 = steps.begin.next.next.next.next.next.next.next.next.next.next.next.next.next

step.validations shouldBe a [Failure[_]]
Expand Down Expand Up @@ -1834,7 +1834,7 @@ class RelyingPartyRegistrationSpec extends FunSpec with Matchers with ScalaCheck
}

it("The test case with unknown attestation fails.") {
val testData = RegistrationTestData.FidoU2f.BasicAttestation.editAttestationObject("fmt", "urgel")
val testData = RegistrationTestData.FidoU2f.BasicAttestation.setAttestationStatementFormat("urgel")
val steps = finishRegistration(
testData = testData,
allowUntrustedAttestation = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ object TestAuthenticator {
clientDataJson: String,
): ByteArray = {
val f = JsonNodeFactory.instance
val attObj = f.objectNode().setAll(Map(
val attObj = f.objectNode().setAll[ObjectNode](Map(
"authData" -> f.binaryNode(authDataBytes.getBytes),
"fmt" -> f.textNode(format),
"attStmt" -> makeAttestationStatement(authDataBytes, clientDataJson),
Expand Down
Loading

0 comments on commit 74ff8c6

Please sign in to comment.