Implement --list and --list-all commands
Requires a MSRV of 1.44 due to the transitive dependency on bitvec 0.19.
This commit is contained in:
@@ -8,10 +8,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.41.0
|
||||
toolchain: 1.44.0
|
||||
override: true
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install libpcsclite-dev
|
||||
@@ -49,10 +49,10 @@ jobs:
|
||||
os: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.41.0
|
||||
toolchain: 1.44.0
|
||||
override: true
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install ${{ matrix.build_deps }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
Generated
+611
-4
@@ -14,12 +14,12 @@ name = "age-core"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/str4d/rage.git?rev=d8fd951e059d9f7116b2b9dd0d176798a11b49f3#d8fd951e059d9f7116b2b9dd0d176798a11b49f3"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.12.3",
|
||||
"c2-chacha",
|
||||
"chacha20poly1305",
|
||||
"cookie-factory",
|
||||
"hkdf",
|
||||
"nom",
|
||||
"nom 5.1.2",
|
||||
"rand",
|
||||
"secrecy",
|
||||
"sha2",
|
||||
@@ -42,7 +42,12 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"age-core",
|
||||
"age-plugin",
|
||||
"bech32",
|
||||
"elliptic-curve",
|
||||
"gumdrop",
|
||||
"p256",
|
||||
"x509-parser 0.9.0",
|
||||
"yubikey-piv",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -51,6 +56,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
@@ -63,6 +74,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.7.2"
|
||||
@@ -75,6 +92,29 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2838fdd79e8776dbe07a106c784b0f8dda571a21b2750a092cc4cbaa653c8e"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium 0.4.1",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.19.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium 0.5.3",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
@@ -84,6 +124,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "c2-chacha"
|
||||
version = "0.3.0"
|
||||
@@ -140,6 +186,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d9162b7289a46e86208d6af2c686ca5bfde445878c41a458a9fac706252d0b"
|
||||
|
||||
[[package]]
|
||||
name = "cookie-factory"
|
||||
version = "0.3.1"
|
||||
@@ -168,6 +220,75 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993a608597367c6377b258c25d7120740f00ed23a2252b729b1932dd7866f908"
|
||||
|
||||
[[package]]
|
||||
name = "der-oid-macro"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e66558629d772c3be040566b7be07be8c8f5aecee95e4a092dfe2efc313277ad"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
"num-bigint 0.3.1",
|
||||
"num-traits",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-oid-macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd17d13ecf875e704369fdbde242483ac769fc18f6af21e43d5a692a079732fc"
|
||||
dependencies = [
|
||||
"nom 6.0.1",
|
||||
"num-bigint 0.3.1",
|
||||
"num-traits",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caca07c50eaae94d43e21f4d14eca5543b6f5f5ce64715e9b7665ac5f5185b4e"
|
||||
dependencies = [
|
||||
"der-oid-macro 0.2.0",
|
||||
"nom 5.1.2",
|
||||
"num-bigint 0.3.1",
|
||||
"num-traits",
|
||||
"proc-macro-hack",
|
||||
"rusticata-macros 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4b1e27396f46037881c39d821660f2ff48797aaa7152a45ded7a93b368a819"
|
||||
dependencies = [
|
||||
"der-oid-macro 0.3.0",
|
||||
"nom 6.0.1",
|
||||
"num-bigint 0.3.1",
|
||||
"num-traits",
|
||||
"proc-macro-hack",
|
||||
"rusticata-macros 3.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "des"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b24e7c748888aa2fa8bce21d8c64a52efc810663285315ac7476f7197a982fae"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
@@ -177,6 +298,38 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396db09c483e7fca5d4fdb9112685632b3e76c9a607a2649c1bf904404a01366"
|
||||
dependencies = [
|
||||
"bitvec 0.18.4",
|
||||
"const-oid",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef"
|
||||
dependencies = [
|
||||
"bitvec 0.18.4",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
@@ -198,6 +351,17 @@ dependencies = [
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gumdrop"
|
||||
version = "0.8.0"
|
||||
@@ -238,6 +402,15 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.4"
|
||||
@@ -257,6 +430,21 @@ version = "0.2.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
@@ -274,13 +462,77 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "6.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0"
|
||||
dependencies = [
|
||||
"bitvec 0.19.4",
|
||||
"lexical-core",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"libm",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"autocfg 1.0.1",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
@@ -290,15 +542,99 @@ version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"autocfg 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2508c8f170e55be68508b1113956a760a82684f42022f8834fb16ca198621211"
|
||||
dependencies = [
|
||||
"der-parser 5.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "280ed58e7e5f3052b6e2f596fa40c7eff4c27c4b6b6deecb5d685ba5c2080980"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06de0548166c258c22bb6bdcff3074eac4b07125040aa74db3f61db87fe5f275"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b8c0d71734018084da0c0354193a5edfb81b20d2d57a92c5b154aefc554a4a"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"crypto-mac",
|
||||
"hmac",
|
||||
"rand",
|
||||
"rand_core",
|
||||
"sha2",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pcsc"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88e09a8d8705a2c9b1ffe1f9dd9580efe3f8e80c19fc9f99038fe99b7bb56c83"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"pcsc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pcsc-sys"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c220d01f863d13d96ca82359d1e81e64a7c6bf0637bcde7b2349630addf0c6"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.6.2"
|
||||
@@ -315,6 +651,12 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
@@ -333,6 +675,18 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64de9a0c5361e034f1aefc9f71a86871ec870e766fe31a009734a989b329286a"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
@@ -374,6 +728,67 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest",
|
||||
"lazy_static",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"pem",
|
||||
"rand",
|
||||
"sha2",
|
||||
"simple_asn1",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a9050636e8a1b487ba1fbe99114021cd7594dde3ce6ed95bfc1691e5b5367b"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7390af60e66c44130b4c5ea85f2555b7ace835d73b4b889c704dc3cb4c0468c8"
|
||||
dependencies = [
|
||||
"nom 6.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
@@ -389,6 +804,25 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce3cdf1b5e620a498ee6f2a171885ac7e22f0e12089ec4b3d22b84921792507c"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpuid-bool 0.1.2",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.2"
|
||||
@@ -402,6 +836,29 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"num-bigint 0.2.6",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -414,6 +871,15 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "subtle-encoding"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.58"
|
||||
@@ -425,6 +891,44 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
@@ -498,8 +1002,111 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
||||
[[package]]
|
||||
name = "x509"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9335b8ff50b6a0de184b3eeb11fdce74224e3af90ca7265012512e73fc999d1a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cookie-factory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a76245c48460d72a3e17ad3a01855c3cae98601bb992091c1c1421c77d1cb27c"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"chrono",
|
||||
"data-encoding",
|
||||
"der-oid-macro 0.2.0",
|
||||
"der-parser 4.1.0",
|
||||
"lazy_static",
|
||||
"nom 5.1.2",
|
||||
"num-bigint 0.3.1",
|
||||
"rusticata-macros 2.1.0",
|
||||
"rustversion",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b22c80f083d860f8e77f44762e9df8c92de7defeb70219ec37f32968cab53e90"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
"chrono",
|
||||
"data-encoding",
|
||||
"der-oid-macro 0.3.0",
|
||||
"der-parser 5.0.0",
|
||||
"lazy_static",
|
||||
"nom 6.0.1",
|
||||
"num-bigint 0.3.1",
|
||||
"oid-registry",
|
||||
"rusticata-macros 3.0.1",
|
||||
"rustversion",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yubikey-piv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "568f3f194d91d4f5bd624983d371927c4d72c1d63be2e702cab2d09cb630a28f"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cookie-factory",
|
||||
"der-parser 4.1.0",
|
||||
"des",
|
||||
"elliptic-curve",
|
||||
"getrandom",
|
||||
"hmac",
|
||||
"log",
|
||||
"nom 5.1.2",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"p256",
|
||||
"p384",
|
||||
"pbkdf2",
|
||||
"pcsc",
|
||||
"rsa",
|
||||
"secrecy",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"subtle-encoding",
|
||||
"x509",
|
||||
"x509-parser 0.8.2",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -13,7 +13,12 @@ edition = "2018"
|
||||
[dependencies]
|
||||
age-core = "0.5"
|
||||
age-plugin = "0.0"
|
||||
bech32 = "0.7.2"
|
||||
elliptic-curve = "0.6"
|
||||
gumdrop = "0.8"
|
||||
p256 = "0.5"
|
||||
x509-parser = "0.9"
|
||||
yubikey-piv = { version = "0.1", features = ["untested"] }
|
||||
|
||||
[patch.crates-io]
|
||||
age-core = { git = "https://github.com/str4d/rage.git", rev = "d8fd951e059d9f7116b2b9dd0d176798a11b49f3" }
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::io;
|
||||
pub enum Error {
|
||||
Io(io::Error),
|
||||
MultipleCommands,
|
||||
YubiKey(yubikey_piv::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
@@ -12,6 +13,12 @@ impl From<io::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<yubikey_piv::error::Error> for Error {
|
||||
fn from(e: yubikey_piv::error::Error) -> Self {
|
||||
Error::YubiKey(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Rust only supports `fn main() -> Result<(), E: Debug>`, so we implement `Debug`
|
||||
// manually to provide the error output we want.
|
||||
impl fmt::Debug for Error {
|
||||
@@ -22,6 +29,18 @@ impl fmt::Debug for Error {
|
||||
f,
|
||||
"Only one of --generate, --identity, --list, --list-all can be specified."
|
||||
)?,
|
||||
Error::YubiKey(e) => match e {
|
||||
yubikey_piv::error::Error::NotFound => {
|
||||
writeln!(f, "Please insert the YubiKey you want to set up")?
|
||||
}
|
||||
e => {
|
||||
writeln!(f, "Error while communicating with YubiKey: {}", e)?;
|
||||
use std::error::Error;
|
||||
if let Some(inner) = e.source() {
|
||||
writeln!(f, "Cause: {}", inner)?;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
|
||||
+90
-2
@@ -1,11 +1,44 @@
|
||||
use age_plugin::run_state_machine;
|
||||
use gumdrop::Options;
|
||||
use yubikey_piv::{
|
||||
certificate::PublicKeyInfo,
|
||||
key::{RetiredSlotId, SlotId},
|
||||
Key, Readers,
|
||||
};
|
||||
|
||||
mod error;
|
||||
mod p256;
|
||||
mod plugin;
|
||||
mod util;
|
||||
|
||||
use error::Error;
|
||||
|
||||
const PLUGIN_NAME: &str = "age-plugin-yubikey";
|
||||
const RECIPIENT_PREFIX: &str = "age1yubikey";
|
||||
|
||||
const USABLE_SLOTS: [RetiredSlotId; 20] = [
|
||||
RetiredSlotId::R1,
|
||||
RetiredSlotId::R2,
|
||||
RetiredSlotId::R3,
|
||||
RetiredSlotId::R4,
|
||||
RetiredSlotId::R5,
|
||||
RetiredSlotId::R6,
|
||||
RetiredSlotId::R7,
|
||||
RetiredSlotId::R8,
|
||||
RetiredSlotId::R9,
|
||||
RetiredSlotId::R10,
|
||||
RetiredSlotId::R11,
|
||||
RetiredSlotId::R12,
|
||||
RetiredSlotId::R13,
|
||||
RetiredSlotId::R14,
|
||||
RetiredSlotId::R15,
|
||||
RetiredSlotId::R16,
|
||||
RetiredSlotId::R17,
|
||||
RetiredSlotId::R18,
|
||||
RetiredSlotId::R19,
|
||||
RetiredSlotId::R20,
|
||||
];
|
||||
|
||||
#[derive(Debug, Options)]
|
||||
struct PluginOptions {
|
||||
#[options(help = "Print this help message and exit.")]
|
||||
@@ -31,6 +64,61 @@ struct PluginOptions {
|
||||
list_all: bool,
|
||||
}
|
||||
|
||||
fn list(all: bool) -> Result<(), Error> {
|
||||
let mut readers = Readers::open()?;
|
||||
|
||||
for reader in readers.iter()? {
|
||||
let mut yubikey = reader.open()?;
|
||||
|
||||
for key in Key::list(&mut yubikey)? {
|
||||
// We only use the retired slots.
|
||||
let slot = match key.slot() {
|
||||
SlotId::Retired(slot) => slot,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
// Only P-256 keys are compatible with us.
|
||||
let recipient = match key.certificate().subject_pki() {
|
||||
PublicKeyInfo::EcP256(pubkey) => match p256::Recipient::from_pubkey(*pubkey) {
|
||||
Some(recipient) => recipient,
|
||||
None => continue,
|
||||
},
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let ((name, pin_policy, touch_policy), created) =
|
||||
match x509_parser::parse_x509_certificate(key.certificate().as_ref())
|
||||
.ok()
|
||||
.and_then(|(_, cert)| {
|
||||
util::extract_name_and_policies(&mut yubikey, &key, &cert, all)
|
||||
.map(|res| (res, cert.validity().not_before.to_rfc2822()))
|
||||
}) {
|
||||
Some(res) => res,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
println!(
|
||||
"# Serial: {}, Slot: {}",
|
||||
yubikey.serial(),
|
||||
// Use 1-indexing in the UI for niceness
|
||||
USABLE_SLOTS.iter().position(|s| s == &slot).unwrap() + 1,
|
||||
);
|
||||
println!("# Name: {}", name);
|
||||
println!("# Created: {}", created);
|
||||
println!("# PIN policy: {}", util::pin_policy_to_str(pin_policy));
|
||||
println!(
|
||||
"# Touch policy: {}",
|
||||
util::touch_policy_to_str(touch_policy)
|
||||
);
|
||||
println!("{}", recipient.to_string());
|
||||
println!();
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let opts = PluginOptions::parse_args_default_or_exit();
|
||||
|
||||
@@ -55,9 +143,9 @@ fn main() -> Result<(), Error> {
|
||||
} else if opts.identity {
|
||||
todo!()
|
||||
} else if opts.list {
|
||||
todo!()
|
||||
list(false)
|
||||
} else if opts.list_all {
|
||||
todo!()
|
||||
list(true)
|
||||
} else {
|
||||
// TODO: CLI identity generation
|
||||
Ok(())
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
use bech32::ToBase32;
|
||||
use elliptic_curve::sec1::EncodedPoint;
|
||||
use p256::NistP256;
|
||||
use std::fmt;
|
||||
|
||||
use crate::RECIPIENT_PREFIX;
|
||||
|
||||
/// Wrapper around a compressed secp256r1 curve point.
|
||||
#[derive(Clone)]
|
||||
pub struct Recipient(EncodedPoint<NistP256>);
|
||||
|
||||
impl fmt::Debug for Recipient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Recipient({:?})", self.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Recipient {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(
|
||||
bech32::encode(RECIPIENT_PREFIX, self.as_bytes().to_base32())
|
||||
.expect("HRP is valid")
|
||||
.as_str(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Recipient {
|
||||
/// Attempts to parse a valid secp256r1 public key from its SEC-1 encoding.
|
||||
pub(crate) fn from_pubkey(pubkey: EncodedPoint<NistP256>) -> Option<Self> {
|
||||
if pubkey.is_compressed() {
|
||||
if pubkey.decompress().is_some().into() {
|
||||
Some(Recipient(pubkey))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(Recipient(pubkey.compress()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the compressed SEC-1 encoding of this public key.
|
||||
pub(crate) fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
use x509_parser::{certificate::X509Certificate, der_parser::oid::Oid};
|
||||
use yubikey_piv::{
|
||||
policy::{PinPolicy, TouchPolicy},
|
||||
Key, YubiKey,
|
||||
};
|
||||
|
||||
use crate::PLUGIN_NAME;
|
||||
|
||||
const POLICY_EXTENSION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 41482, 3, 8];
|
||||
|
||||
pub(crate) fn pin_policy_to_str(policy: Option<PinPolicy>) -> &'static str {
|
||||
match policy {
|
||||
Some(PinPolicy::Always) => "Always (A PIN is required for every decryption, if set)",
|
||||
Some(PinPolicy::Once) => "Once (A PIN is required once per session, if set)",
|
||||
Some(PinPolicy::Never) => "Never (A PIN is NOT required to decrypt)",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn touch_policy_to_str(policy: Option<TouchPolicy>) -> &'static str {
|
||||
match policy {
|
||||
Some(TouchPolicy::Always) => "Always (A physical touch is required for every decryption)",
|
||||
Some(TouchPolicy::Cached) => {
|
||||
"Cached (A physical touch is required for decryption, and is cached for 15 seconds)"
|
||||
}
|
||||
Some(TouchPolicy::Never) => "Never (A physical touch is NOT required to decrypt)",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_name(cert: &X509Certificate, all: bool) -> Option<(String, bool)> {
|
||||
// Look at Subject Organization to determine if we created this.
|
||||
match cert.subject().iter_organization().next() {
|
||||
Some(org) if org.as_str() == Ok(PLUGIN_NAME) => {
|
||||
// We store the identity name as a Common Name attribute.
|
||||
let name = cert
|
||||
.subject()
|
||||
.iter_common_name()
|
||||
.next()
|
||||
.and_then(|cn| cn.as_str().ok())
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_default(); // TODO: This should always be present.
|
||||
|
||||
Some((name, true))
|
||||
}
|
||||
_ => {
|
||||
// Not one of ours, but we've already filtered for compatibility.
|
||||
if !all {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Display the entire subject.
|
||||
let name = cert.subject().to_string();
|
||||
|
||||
Some((name, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extract_name_and_policies(
|
||||
yubikey: &mut YubiKey,
|
||||
key: &Key,
|
||||
cert: &X509Certificate,
|
||||
all: bool,
|
||||
) -> Option<(String, Option<PinPolicy>, Option<TouchPolicy>)> {
|
||||
// We store the PIN and touch policies for identities in their certificates
|
||||
// using the same certificate extension as PIV attestations.
|
||||
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
|
||||
let policies = |c: &X509Certificate| {
|
||||
c.extensions()
|
||||
.get(&Oid::from(POLICY_EXTENSION_OID).unwrap())
|
||||
// If the encoded extension doesn't have 2 bytes, we assume it is invalid.
|
||||
.filter(|policy| policy.value.len() >= 2)
|
||||
.map(|policy| {
|
||||
// We should only ever see one of three values for either policy, but
|
||||
// handle unknown values just in case.
|
||||
let pin_policy = match policy.value[0] {
|
||||
0x01 => Some(PinPolicy::Never),
|
||||
0x02 => Some(PinPolicy::Once),
|
||||
0x03 => Some(PinPolicy::Always),
|
||||
_ => None,
|
||||
};
|
||||
let touch_policy = match policy.value[1] {
|
||||
0x01 => Some(TouchPolicy::Never),
|
||||
0x02 => Some(TouchPolicy::Always),
|
||||
0x03 => Some(TouchPolicy::Cached),
|
||||
_ => None,
|
||||
};
|
||||
(pin_policy, touch_policy)
|
||||
})
|
||||
.unwrap_or((None, None))
|
||||
};
|
||||
|
||||
extract_name(cert, all).map(|(name, ours)| {
|
||||
if ours {
|
||||
let (pin_policy, touch_policy) = policies(&cert);
|
||||
(name, pin_policy, touch_policy)
|
||||
} else {
|
||||
// We can extract the PIN and touch policies via an attestation. This
|
||||
// is slow, but the user has asked for all compatible keys, so...
|
||||
let (pin_policy, touch_policy) = yubikey_piv::key::attest(yubikey, key.slot())
|
||||
.ok()
|
||||
.and_then(|buf| {
|
||||
x509_parser::parse_x509_certificate(&buf)
|
||||
.map(|(_, c)| policies(&c))
|
||||
.ok()
|
||||
})
|
||||
.unwrap_or((None, None));
|
||||
|
||||
(name, pin_policy, touch_policy)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user