diff --git a/app/build.gradle b/app/build.gradle
index b99601a..90dc906 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -214,7 +214,6 @@ dependencies {
implementation 'org.apache.commons:commons-text:1.6'
implementation 'org.whispersystems:curve25519-java:0.5.0'
- implementation 'org.bouncycastle:bcutil-jdk15on:1.70'
implementation 'com.google.zxing:core:3.3.3'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 2708626..fa114c6 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -57,8 +57,4 @@
-keep class org.whispersystems.curve25519.NativeCurve25519Provider {}
-keep class org.whispersystems.curve25519.JavaCurve25519Provider {}
-keep class org.whispersystems.curve25519.J2meCurve25519Provider {}
--keep class org.whispersystems.curve25519.OpportunisticCurve25519Provider {}
-
-# bouncycastle
--keep class org.bouncycastle.jcajce.provider.** { *; }
--keep class org.bouncycastle.jce.provider.** { *; }
\ No newline at end of file
+-keep class org.whispersystems.curve25519.OpportunisticCurve25519Provider {}
\ No newline at end of file
diff --git a/app/src/main/java/io/timelimit/android/u2f/U2FSignatureValidation.kt b/app/src/main/java/io/timelimit/android/u2f/U2FSignatureValidation.kt
index 7ba8835..0633e9a 100644
--- a/app/src/main/java/io/timelimit/android/u2f/U2FSignatureValidation.kt
+++ b/app/src/main/java/io/timelimit/android/u2f/U2FSignatureValidation.kt
@@ -16,26 +16,13 @@
package io.timelimit.android.u2f
import io.timelimit.android.u2f.protocol.U2FResponse
-import org.bouncycastle.asn1.sec.SECNamedCurves
-import org.bouncycastle.crypto.CryptoException
-import org.bouncycastle.jce.provider.BouncyCastleProvider
-import org.bouncycastle.jce.spec.ECParameterSpec
-import org.bouncycastle.jce.spec.ECPublicKeySpec
import java.security.KeyFactory
-import java.security.Security
import java.security.Signature
+import java.security.spec.InvalidKeySpecException
+import java.security.spec.X509EncodedKeySpec
object U2FSignatureValidation {
- init {
- Security.removeProvider("BC")
- Security.addProvider(BouncyCastleProvider())
- }
-
- private val curve = SECNamedCurves.getByName("secp256r1")
- private val ecParamSpec = ECParameterSpec(curve.getCurve(), curve.getG(), curve.getN(), curve.getH())
-
- // based on https://github.com/Yubico/java-u2flib-server/blob/dd44d3cdce4eeaeb517f2acd1fd520d5a42ce752/u2flib-server-core/src/main/java/com/yubico/u2f/crypto/BouncyCastleCrypto.java
fun validate(
applicationId: ByteArray,
challenge: ByteArray,
@@ -54,22 +41,22 @@ object U2FSignatureValidation {
if (publicKey.size != 65 || publicKey[0] != 4.toByte()) return false
- val point = curve.getCurve().decodePoint(publicKey)
+ val verifier = Signature.getInstance("SHA256withECDSA")
- val decodedPublicKey = KeyFactory.getInstance("EC", "BC").generatePublic(
- ECPublicKeySpec(point, ecParamSpec)
+ verifier.initVerify(
+ KeyFactory
+ .getInstance("EC")
+ .generatePublic(
+ X509EncodedKeySpec(
+ byteArrayOf(48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61, 3, 1, 7, 3, 66, 0) + publicKey
+ )
+ )
)
- val verifier = Signature.getInstance("SHA256withECDSA", "BC")
-
- verifier.initVerify(decodedPublicKey)
-
verifier.update(signedData)
return verifier.verify(response.signature)
- } catch (ex: CryptoException) {
- return false
- } catch (ex: IllegalArgumentException) {
+ } catch (ex: InvalidKeySpecException) {
return false
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 87eae0b..4a2732e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -107,10 +107,6 @@
(Apache License 2.0)
\nWire
(Apache License 2.0)
- \nBouncy Castle
- (MIT License)
- \njava-u2flib-server
- (BSD License)
Error diagnose
diff --git a/contrib/publickeymetadata.js b/contrib/publickeymetadata.js
new file mode 100644
index 0000000..a1b5f19
--- /dev/null
+++ b/contrib/publickeymetadata.js
@@ -0,0 +1,95 @@
+// https://www.rfc-editor.org/rfc/rfc5280.html#appendix-A.1
+// https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/
+// https://www.ietf.org/rfc/rfc5480.txt
+
+/*
+SubjectPublicKeyInfo ::= SEQUENCE {
+ algorithm AlgorithmIdentifier,
+ subjectPublicKey BIT STRING }
+
+
+AlgorithmIdentifier ::= SEQUENCE {
+ algorithm OBJECT IDENTIFIER,
+ parameters ANY DEFINED BY algorithm OPTIONAL }
+ -- contains a value of the type
+ -- registered for use with the
+ -- algorithm object identifier value
+
+id-ecPublicKey OBJECT IDENTIFIER ::= {
+ iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
+
+secp256r1 OBJECT IDENTIFIER ::= {
+ iso(1) member-body(2) us(840) ansi-X9-62(10045) curves(3)
+ prime(1) 7 }
+*/
+
+function encodeValue(type, valueBuffer) {
+ const length = valueBuffer.length
+
+ if (length >= 128) throw new Error('long encoding not supported')
+
+ const typeBuffer = Buffer.from([type])
+ const lengthBuffer = Buffer.from([length])
+
+ return Buffer.concat([typeBuffer, lengthBuffer, valueBuffer])
+}
+
+function encodeBitstring(valueBuffer) {
+ return encodeValue(3, Buffer.concat([Buffer.from([0 /* no unused bits */]), valueBuffer]))
+}
+
+function encodeOid(value) {
+ const numbers = [
+ value[0] * 40 + value[1],
+ ...value.slice(2)
+ ]
+
+ const result = []
+
+ for (let number of numbers) {
+ const tempResult = []
+ while (number) { tempResult.push(128 | (number % 128)); number = Math.floor(number / 128) }
+ tempResult.reverse()
+ tempResult[tempResult.length - 1] &= 127
+
+ for (const item of tempResult) result.push(item)
+ }
+
+ return encodeValue(6, Buffer.from(result))
+}
+
+function encodeSequence(itemBuffers) {
+ return encodeValue(0x30, Buffer.concat(itemBuffers))
+}
+
+function encodeAlogrithmIdentifier() {
+ return encodeSequence([
+ encodeOid([1, 2, 840, 10045, 2, 1]),
+ encodeOid([1, 2, 840, 10045, 3, 1, 7])
+ ])
+}
+
+function encodePublicKeyInfo() {
+ const dummyKey = Buffer.alloc(65)
+ for (let i = 0; i < dummyKey.length; i++) dummyKey.writeUInt8(255, i)
+
+ return encodeSequence([
+ encodeAlogrithmIdentifier(),
+ encodeBitstring(dummyKey)
+ ])
+}
+
+const result = encodePublicKeyInfo()
+
+// compare result with https://github.com/ashtuchkin/u2f/blob/2e45ea40acd8c3ad6c113cd1b4e0558acc4cda3a/index.js#L21
+if (result.toString('hex') !== '3059301306072a8648ce3d020106082a8648ce3d030107034200' + ('ff'.repeat(65))) throw new Error()
+
+const prefix = result.slice(0, result.length - 65)
+
+console.log(prefix.toString('hex'))
+console.log(prefix.toString('base64'))
+
+const prefixNumbers = []; for (let i = 0; i < prefix.length; i++) prefixNumbers.push(prefix.readInt8(i))
+const kotlinByteArray = 'byteArrayOf(' + prefixNumbers.join(', ') + ')'
+
+console.log(kotlinByteArray)