├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── backends.rs ├── commands.rs ├── database.rs ├── encrypters.rs ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /secrets.yaml 3 | /secrets 4 | /hips 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.62" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "base64" 39 | version = "0.13.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 42 | 43 | [[package]] 44 | name = "bitflags" 45 | version = "1.3.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 48 | 49 | [[package]] 50 | name = "bumpalo" 51 | version = "3.11.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" 54 | 55 | [[package]] 56 | name = "cc" 57 | version = "1.0.73" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 60 | 61 | [[package]] 62 | name = "cfg-if" 63 | version = "1.0.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 66 | 67 | [[package]] 68 | name = "clap" 69 | version = "3.2.17" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" 72 | dependencies = [ 73 | "atty", 74 | "bitflags", 75 | "clap_derive", 76 | "clap_lex", 77 | "indexmap", 78 | "once_cell", 79 | "strsim", 80 | "termcolor", 81 | "textwrap", 82 | ] 83 | 84 | [[package]] 85 | name = "clap_derive" 86 | version = "3.2.17" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" 89 | dependencies = [ 90 | "heck", 91 | "proc-macro-error", 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_lex" 99 | version = "0.2.4" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 102 | dependencies = [ 103 | "os_str_bytes", 104 | ] 105 | 106 | [[package]] 107 | name = "clipboard-win" 108 | version = "4.4.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" 111 | dependencies = [ 112 | "error-code", 113 | "str-buf", 114 | "winapi", 115 | ] 116 | 117 | [[package]] 118 | name = "clishe" 119 | version = "0.2.7" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "c7d0a35529248d4ac3a102b3839dfe10d85c2e00a2b5121733ec21d4d8180275" 122 | dependencies = [ 123 | "anyhow", 124 | "clap", 125 | "paste 1.0.8", 126 | "rustyline", 127 | "shellwords", 128 | ] 129 | 130 | [[package]] 131 | name = "dirs-next" 132 | version = "2.0.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 135 | dependencies = [ 136 | "cfg-if", 137 | "dirs-sys-next", 138 | ] 139 | 140 | [[package]] 141 | name = "dirs-sys-next" 142 | version = "0.1.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 145 | dependencies = [ 146 | "libc", 147 | "redox_users", 148 | "winapi", 149 | ] 150 | 151 | [[package]] 152 | name = "endian-type" 153 | version = "0.1.2" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 156 | 157 | [[package]] 158 | name = "errno" 159 | version = "0.2.8" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 162 | dependencies = [ 163 | "errno-dragonfly", 164 | "libc", 165 | "winapi", 166 | ] 167 | 168 | [[package]] 169 | name = "errno-dragonfly" 170 | version = "0.1.2" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 173 | dependencies = [ 174 | "cc", 175 | "libc", 176 | ] 177 | 178 | [[package]] 179 | name = "error-code" 180 | version = "2.3.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 183 | dependencies = [ 184 | "libc", 185 | "str-buf", 186 | ] 187 | 188 | [[package]] 189 | name = "fd-lock" 190 | version = "3.0.6" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" 193 | dependencies = [ 194 | "cfg-if", 195 | "rustix", 196 | "windows-sys", 197 | ] 198 | 199 | [[package]] 200 | name = "getrandom" 201 | version = "0.2.7" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 204 | dependencies = [ 205 | "cfg-if", 206 | "libc", 207 | "wasi", 208 | ] 209 | 210 | [[package]] 211 | name = "hashbrown" 212 | version = "0.12.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 215 | 216 | [[package]] 217 | name = "heck" 218 | version = "0.4.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 221 | 222 | [[package]] 223 | name = "hermit-abi" 224 | version = "0.1.19" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 227 | dependencies = [ 228 | "libc", 229 | ] 230 | 231 | [[package]] 232 | name = "hips" 233 | version = "0.4.2" 234 | dependencies = [ 235 | "anyhow", 236 | "base64", 237 | "clap", 238 | "clishe", 239 | "paste 0.1.18", 240 | "ring", 241 | "serde", 242 | "serde_json", 243 | "serde_yaml", 244 | "snailquote", 245 | "tinytemplate", 246 | ] 247 | 248 | [[package]] 249 | name = "indexmap" 250 | version = "1.9.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 253 | dependencies = [ 254 | "autocfg", 255 | "hashbrown", 256 | ] 257 | 258 | [[package]] 259 | name = "io-lifetimes" 260 | version = "0.7.3" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" 263 | 264 | [[package]] 265 | name = "itoa" 266 | version = "1.0.3" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 269 | 270 | [[package]] 271 | name = "js-sys" 272 | version = "0.3.59" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 275 | dependencies = [ 276 | "wasm-bindgen", 277 | ] 278 | 279 | [[package]] 280 | name = "lazy_static" 281 | version = "1.4.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 284 | 285 | [[package]] 286 | name = "libc" 287 | version = "0.2.132" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" 290 | 291 | [[package]] 292 | name = "linux-raw-sys" 293 | version = "0.0.46" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" 296 | 297 | [[package]] 298 | name = "log" 299 | version = "0.4.17" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 302 | dependencies = [ 303 | "cfg-if", 304 | ] 305 | 306 | [[package]] 307 | name = "memchr" 308 | version = "2.5.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 311 | 312 | [[package]] 313 | name = "nibble_vec" 314 | version = "0.1.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 317 | dependencies = [ 318 | "smallvec", 319 | ] 320 | 321 | [[package]] 322 | name = "nix" 323 | version = "0.24.2" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" 326 | dependencies = [ 327 | "bitflags", 328 | "cfg-if", 329 | "libc", 330 | ] 331 | 332 | [[package]] 333 | name = "once_cell" 334 | version = "1.13.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" 337 | 338 | [[package]] 339 | name = "os_str_bytes" 340 | version = "6.3.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 343 | 344 | [[package]] 345 | name = "paste" 346 | version = "0.1.18" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" 349 | dependencies = [ 350 | "paste-impl", 351 | "proc-macro-hack", 352 | ] 353 | 354 | [[package]] 355 | name = "paste" 356 | version = "1.0.8" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" 359 | 360 | [[package]] 361 | name = "paste-impl" 362 | version = "0.1.18" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" 365 | dependencies = [ 366 | "proc-macro-hack", 367 | ] 368 | 369 | [[package]] 370 | name = "proc-macro-error" 371 | version = "1.0.4" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 374 | dependencies = [ 375 | "proc-macro-error-attr", 376 | "proc-macro2", 377 | "quote", 378 | "syn", 379 | "version_check", 380 | ] 381 | 382 | [[package]] 383 | name = "proc-macro-error-attr" 384 | version = "1.0.4" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 387 | dependencies = [ 388 | "proc-macro2", 389 | "quote", 390 | "version_check", 391 | ] 392 | 393 | [[package]] 394 | name = "proc-macro-hack" 395 | version = "0.5.19" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 398 | 399 | [[package]] 400 | name = "proc-macro2" 401 | version = "1.0.43" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 404 | dependencies = [ 405 | "unicode-ident", 406 | ] 407 | 408 | [[package]] 409 | name = "quote" 410 | version = "1.0.21" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 413 | dependencies = [ 414 | "proc-macro2", 415 | ] 416 | 417 | [[package]] 418 | name = "radix_trie" 419 | version = "0.2.1" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 422 | dependencies = [ 423 | "endian-type", 424 | "nibble_vec", 425 | ] 426 | 427 | [[package]] 428 | name = "redox_syscall" 429 | version = "0.2.16" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 432 | dependencies = [ 433 | "bitflags", 434 | ] 435 | 436 | [[package]] 437 | name = "redox_users" 438 | version = "0.4.3" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 441 | dependencies = [ 442 | "getrandom", 443 | "redox_syscall", 444 | "thiserror", 445 | ] 446 | 447 | [[package]] 448 | name = "regex" 449 | version = "1.6.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 452 | dependencies = [ 453 | "aho-corasick", 454 | "memchr", 455 | "regex-syntax", 456 | ] 457 | 458 | [[package]] 459 | name = "regex-syntax" 460 | version = "0.6.27" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 463 | 464 | [[package]] 465 | name = "ring" 466 | version = "0.16.20" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 469 | dependencies = [ 470 | "cc", 471 | "libc", 472 | "once_cell", 473 | "spin", 474 | "untrusted", 475 | "web-sys", 476 | "winapi", 477 | ] 478 | 479 | [[package]] 480 | name = "rustix" 481 | version = "0.35.9" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" 484 | dependencies = [ 485 | "bitflags", 486 | "errno", 487 | "io-lifetimes", 488 | "libc", 489 | "linux-raw-sys", 490 | "windows-sys", 491 | ] 492 | 493 | [[package]] 494 | name = "rustyline" 495 | version = "10.0.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e" 498 | dependencies = [ 499 | "bitflags", 500 | "cfg-if", 501 | "clipboard-win", 502 | "dirs-next", 503 | "fd-lock", 504 | "libc", 505 | "log", 506 | "memchr", 507 | "nix", 508 | "radix_trie", 509 | "scopeguard", 510 | "unicode-segmentation", 511 | "unicode-width", 512 | "utf8parse", 513 | "winapi", 514 | ] 515 | 516 | [[package]] 517 | name = "ryu" 518 | version = "1.0.11" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 521 | 522 | [[package]] 523 | name = "scopeguard" 524 | version = "1.1.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 527 | 528 | [[package]] 529 | name = "serde" 530 | version = "1.0.144" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" 533 | dependencies = [ 534 | "serde_derive", 535 | ] 536 | 537 | [[package]] 538 | name = "serde_derive" 539 | version = "1.0.144" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" 542 | dependencies = [ 543 | "proc-macro2", 544 | "quote", 545 | "syn", 546 | ] 547 | 548 | [[package]] 549 | name = "serde_json" 550 | version = "1.0.85" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 553 | dependencies = [ 554 | "itoa", 555 | "ryu", 556 | "serde", 557 | ] 558 | 559 | [[package]] 560 | name = "serde_yaml" 561 | version = "0.9.10" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "7a09f551ccc8210268ef848f0bab37b306e87b85b2e017b899e7fb815f5aed62" 564 | dependencies = [ 565 | "indexmap", 566 | "itoa", 567 | "ryu", 568 | "serde", 569 | "unsafe-libyaml", 570 | ] 571 | 572 | [[package]] 573 | name = "shellwords" 574 | version = "1.1.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "89e515aa4699a88148ed5ef96413ceef0048ce95b43fbc955a33bde0a70fcae6" 577 | dependencies = [ 578 | "lazy_static", 579 | "regex", 580 | ] 581 | 582 | [[package]] 583 | name = "smallvec" 584 | version = "1.9.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 587 | 588 | [[package]] 589 | name = "snailquote" 590 | version = "0.3.1" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "ec62a949bda7f15800481a711909f946e1204f2460f89210eaf7f57730f88f86" 593 | dependencies = [ 594 | "thiserror", 595 | "unicode_categories", 596 | ] 597 | 598 | [[package]] 599 | name = "spin" 600 | version = "0.5.2" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 603 | 604 | [[package]] 605 | name = "str-buf" 606 | version = "1.0.6" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 609 | 610 | [[package]] 611 | name = "strsim" 612 | version = "0.10.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 615 | 616 | [[package]] 617 | name = "syn" 618 | version = "1.0.99" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 621 | dependencies = [ 622 | "proc-macro2", 623 | "quote", 624 | "unicode-ident", 625 | ] 626 | 627 | [[package]] 628 | name = "termcolor" 629 | version = "1.1.3" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 632 | dependencies = [ 633 | "winapi-util", 634 | ] 635 | 636 | [[package]] 637 | name = "textwrap" 638 | version = "0.15.0" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 641 | 642 | [[package]] 643 | name = "thiserror" 644 | version = "1.0.32" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 647 | dependencies = [ 648 | "thiserror-impl", 649 | ] 650 | 651 | [[package]] 652 | name = "thiserror-impl" 653 | version = "1.0.32" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 656 | dependencies = [ 657 | "proc-macro2", 658 | "quote", 659 | "syn", 660 | ] 661 | 662 | [[package]] 663 | name = "tinytemplate" 664 | version = "1.2.1" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 667 | dependencies = [ 668 | "serde", 669 | "serde_json", 670 | ] 671 | 672 | [[package]] 673 | name = "unicode-ident" 674 | version = "1.0.3" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 677 | 678 | [[package]] 679 | name = "unicode-segmentation" 680 | version = "1.9.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 683 | 684 | [[package]] 685 | name = "unicode-width" 686 | version = "0.1.9" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 689 | 690 | [[package]] 691 | name = "unicode_categories" 692 | version = "0.1.1" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 695 | 696 | [[package]] 697 | name = "unsafe-libyaml" 698 | version = "0.2.2" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0" 701 | 702 | [[package]] 703 | name = "untrusted" 704 | version = "0.7.1" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 707 | 708 | [[package]] 709 | name = "utf8parse" 710 | version = "0.2.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 713 | 714 | [[package]] 715 | name = "version_check" 716 | version = "0.9.4" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 719 | 720 | [[package]] 721 | name = "wasi" 722 | version = "0.11.0+wasi-snapshot-preview1" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 725 | 726 | [[package]] 727 | name = "wasm-bindgen" 728 | version = "0.2.82" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 731 | dependencies = [ 732 | "cfg-if", 733 | "wasm-bindgen-macro", 734 | ] 735 | 736 | [[package]] 737 | name = "wasm-bindgen-backend" 738 | version = "0.2.82" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 741 | dependencies = [ 742 | "bumpalo", 743 | "log", 744 | "once_cell", 745 | "proc-macro2", 746 | "quote", 747 | "syn", 748 | "wasm-bindgen-shared", 749 | ] 750 | 751 | [[package]] 752 | name = "wasm-bindgen-macro" 753 | version = "0.2.82" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 756 | dependencies = [ 757 | "quote", 758 | "wasm-bindgen-macro-support", 759 | ] 760 | 761 | [[package]] 762 | name = "wasm-bindgen-macro-support" 763 | version = "0.2.82" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 766 | dependencies = [ 767 | "proc-macro2", 768 | "quote", 769 | "syn", 770 | "wasm-bindgen-backend", 771 | "wasm-bindgen-shared", 772 | ] 773 | 774 | [[package]] 775 | name = "wasm-bindgen-shared" 776 | version = "0.2.82" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 779 | 780 | [[package]] 781 | name = "web-sys" 782 | version = "0.3.59" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" 785 | dependencies = [ 786 | "js-sys", 787 | "wasm-bindgen", 788 | ] 789 | 790 | [[package]] 791 | name = "winapi" 792 | version = "0.3.9" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 795 | dependencies = [ 796 | "winapi-i686-pc-windows-gnu", 797 | "winapi-x86_64-pc-windows-gnu", 798 | ] 799 | 800 | [[package]] 801 | name = "winapi-i686-pc-windows-gnu" 802 | version = "0.4.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 805 | 806 | [[package]] 807 | name = "winapi-util" 808 | version = "0.1.5" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 811 | dependencies = [ 812 | "winapi", 813 | ] 814 | 815 | [[package]] 816 | name = "winapi-x86_64-pc-windows-gnu" 817 | version = "0.4.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 820 | 821 | [[package]] 822 | name = "windows-sys" 823 | version = "0.36.1" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 826 | dependencies = [ 827 | "windows_aarch64_msvc", 828 | "windows_i686_gnu", 829 | "windows_i686_msvc", 830 | "windows_x86_64_gnu", 831 | "windows_x86_64_msvc", 832 | ] 833 | 834 | [[package]] 835 | name = "windows_aarch64_msvc" 836 | version = "0.36.1" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 839 | 840 | [[package]] 841 | name = "windows_i686_gnu" 842 | version = "0.36.1" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 845 | 846 | [[package]] 847 | name = "windows_i686_msvc" 848 | version = "0.36.1" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 851 | 852 | [[package]] 853 | name = "windows_x86_64_gnu" 854 | version = "0.36.1" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 857 | 858 | [[package]] 859 | name = "windows_x86_64_msvc" 860 | version = "0.36.1" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 863 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hips" 3 | license = "MIT" 4 | readme = "README.md" 5 | authors = ["Louis Feuvrier "] 6 | repository = "https://github.com/mqnfred/hips" 7 | description = "Manage secrets alongside your code" 8 | categories = ["command-line-utilities", "cryptography"] 9 | keywords = ["secrets", "manager", "database", "encryption"] 10 | version = "0.4.2" 11 | edition = "2018" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | anyhow = "^1" 17 | base64 = "^0" 18 | clap = { version = "^3", features = ["derive"] } 19 | clishe = "^0" 20 | paste = "^0" 21 | ring = "^0" 22 | serde = { version = "1.0", features = ["derive"] } 23 | serde_json = "^1" 24 | serde_yaml = "^0" 25 | snailquote = "^0" 26 | tinytemplate = "^1" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hide in plain sight 2 | 3 | [![crates.io](https://img.shields.io/crates/v/hips)](https://crates.io/crates/hips) 4 | 5 | `hips` is a small, self-contained utility that enables users to store their 6 | secrets encrypted alongside their code. It can be used as a binary or a 7 | [library][7] depending on your needs. What are you interested in knowing? 8 | 9 | 1. [Why do this?](#philosophy) 10 | 2. [Let's try it out](#install) 11 | 3. [Tutorial](#tutorial) 12 | 4. [Is this even safe](#safety) 13 | 14 | ## Philosophy 15 | 16 | `me+code=secrets` 17 | 18 | For all the small shops out there, the low profile targets with a limited 19 | amount of individual developers involved, we suggest tracking your secrets 20 | alongside your code, in a file or folder database. 21 | 22 | This will reduce sources of truth in your distributed system by 1 and help with 23 | "infrastructure as code" by making access to the secrets a local affair. You 24 | will not have to depend on any kind of remote infrastructure for your secrets. 25 | 26 | This solution will not work well for you if you: 27 | 28 | - Have many developers (master-password strategy does not scale well) 29 | - High scale/complexity infra (a need for secrets as a service) 30 | - High profile shops (compliance reasons, need insurances) 31 | 32 | In this database, you could store your AWS credentials or the ssh key you use 33 | to connect to your production. You could store secrets needed by your serving 34 | layer to authenticate with your database and push those using a tool like 35 | ansible or ssh. 36 | 37 | ## Install 38 | 39 | You will need [cargo][1] to install `hips`. Once you have it, do the following: 40 | 41 | ```sh 42 | export PATH=$PATH:$HOME/.cargo/bin 43 | cargo install hips 44 | hips --help 45 | ``` 46 | 47 | ## Tutorial 48 | 49 | In this tutorial you will learn about all the different commands and database 50 | formats that `hips` supports. 51 | 52 | ### Database and password configuration 53 | 54 | We use environment variables to pass the database and password to `hips`: 55 | 56 | ``` 57 | $ export HIPS_DATABASE=secrets.yaml 58 | $ export HIPS_PASSWORD=pw 59 | ``` 60 | 61 | ### Store, Load, List, Remove, Rename 62 | 63 | `store` takes a name and a secret and stores them in the database. 64 | 65 | ``` 66 | $ hips store aws_access_key_id BUIO1IXUAK3OQ9ACAHSX 67 | $ hips store aws_secret_access_key UwioixhaklufhhWbaXoSLwbxb2dj7/AJs92bdsXh 68 | $ cat secrets.yaml 69 | --- 70 | - name: aws_access_key_id 71 | secret: c58C04qkTDQhg86piVlmg7EXcz66i3C3GSdHjmZW5v2Pa6Froo69gbuDNSICXh4w 72 | salt: OH3/mX3e/3ODwSLBYzFiK92PztSgLLmIf5S8mqenwXo= 73 | - name: aws_secret_access_key 74 | secret: asrhBl8bMTPGj8Cua6LzyseRBLmhmNrivaCjW53NcNRUyKSYOkoLdq9PHSPHKdgosO6/acOn3hv+vnkciwLj0tio0ac= 75 | salt: 4GP2GtoRhaf6NKtanBm9aLjUefuNH+otFDFfHF1Utns=% 76 | ``` 77 | 78 | `load` takes a name and prints out the matching secret. 79 | 80 | ``` 81 | $ hips load aws_access_key_id 82 | BUIO1IXUAK3OQ9ACAHSX 83 | $ hips load aws_secret_access_key 84 | UwioixhaklufhhWbaXoSLwbxb2dj7/AJs92bdsXh 85 | ``` 86 | 87 | `list` prints the names of the secrets stored in the db sorted alphabetically. 88 | 89 | ``` 90 | $ hips ls 91 | aws_access_key_id 92 | aws_secret_access_key 93 | ``` 94 | 95 | `remove` (`rm`) takes a name and removes that secret from the database. 96 | 97 | ``` 98 | $ hips store remove_me_soon unimportant-secret 99 | $ hips ls 100 | aws_access_key_id 101 | aws_secret_access_key 102 | remove_me_soon 103 | $ hips remove remove_me_soon 104 | $ hips ls 105 | aws_access_key_id 106 | aws_secret_access_key 107 | ``` 108 | 109 | `rename` renames a secret based on a (orig, dest) name pair 110 | 111 | ``` 112 | $ hips store move_me_please unimportant-secret 113 | $ hips ls 114 | aws_access_key_id 115 | aws_secret_access_key 116 | move_me_please 117 | $ hips rename move_me_please thats_better 118 | $ hips ls 119 | aws_access_key_id 120 | aws_secret_access_key 121 | thats_better 122 | ``` 123 | 124 | ### Rotate 125 | 126 | You can `rotate` (`rot`) the secrets database in one command, re-encrypting 127 | everything using a different password. 128 | 129 | ``` 130 | $ cat secrets.yaml | grep secret: 131 | secret: c58C04qkTDQhg86piVlmg7EXcz66i3C3GSdHjmZW5v2Pa6Froo69gbuDNSICXh4w 132 | secret: asrhBl8bMTPGj8Cua6LzyseRBLmhmNrivaCjW53NcNRUyKSYOkoLdq9PHSPHKdgosO6/acOn3hv+vnkciwLj0tio0ac= 133 | $ hips rotate new-pw 134 | $ cat secrets.yaml | grep secret: 135 | secret: Sb8BznQqjYr+q+lis2uVKPZ/j+qmNIMuXbjr/MElIAYkupyUCGHPbY+N/NTpTxKr 136 | secret: FlWQvkzidFa8mStgoEbQXt4MobHPQFT7NFnImKIjgfNZ7xFhPGjj3kD0z3x4YNzLTjBMJykk57JooYCojhOH/GlqeEk= 137 | $ export HIPS_PASSWORD=new-pw 138 | $ hips load aws_access_key_id 139 | BUIO1IXUAK3OQ9ACAHSX 140 | ``` 141 | 142 | You can see here that the encrypted secrets and salt are different from the 143 | previous `secrets.yaml` database. We can now read all the secrets using the new 144 | password. 145 | 146 | ### Template 147 | 148 | Many times when exporting secrets to production, they need to be displayed in a 149 | specific manner, as part of a configuration file and such. We use the [tiny 150 | template][2] library for this purpose. See their neat [syntax page][3] for more 151 | information. We will cover our templating capabilities in the following two 152 | examples. 153 | 154 | #### AWS credentials file 155 | 156 | Let's generate the `.aws/credentials`, where amazon secrets are conventionally 157 | stored. We'll use the "map" feature of the templating framework, which allows 158 | us to print out specific secrets by name. 159 | 160 | ``` 161 | $ hips template '[default]\naws_access_key_id={map.aws_access_key_id}\naws_secret_access_key={map.aws_secret_access_key}' 162 | [default] 163 | aws_access_key_id=BUIO1IXUAK3OQ9ACAHSX 164 | aws_secret_access_key=UwioixhaklufhhWbaXoSLwbxb2dj7/AJs92bdsXh 165 | ``` 166 | 167 | #### Shell script loading all secrets 168 | 169 | This time, since our template is a bit more complex, we'll store it in a file: 170 | 171 | ``` 172 | $ cat shell-template 173 | #!/bin/sh 174 | {{ for secret in list -}} 175 | {{- if not @first }}\n{{ endif -}} 176 | export {secret.name|capitalize}={secret.secret}; 177 | {{- endfor -}} 178 | ``` 179 | 180 | You can find more information about this syntax [here][3]. This will yield the 181 | following shell script: 182 | 183 | ``` 184 | $ hips template shell-template 185 | #!/bin/sh 186 | export AWS_ACCESS_KEY_ID=BUIO1IXUAK3OQ9ACAHSX; 187 | export AWS_SECRET_ACCESS_KEY=UwioixhaklufhhWbaXoSLwbxb2dj7/AJs92bdsXh; 188 | ``` 189 | 190 | ### Database formats 191 | 192 | Up until now, we have been using a yaml file as database. We support multiple 193 | formats however: 194 | 195 | - As a directory hierarchy (no extension) 196 | - As a single yaml file (`.yaml` extension) 197 | 198 | If we were to repeat the experiment above with a directory hierarchy under 199 | `secrets/`, our database would look like this: 200 | 201 | ``` 202 | $ tree secrets/ 203 | secrets 204 | ├── aws_access_key_id/ 205 | │ ├── salt 206 | │ └── secret 207 | └── aws_secret_access_key/ 208 | ├── salt 209 | └── secret 210 | $ cat secrets/aws_access_key_id/secret 211 | Sb8BznQqjYr+q+lis2uVKPZ/j+qmNIMuXbjr/MElIAYkupyUCGHPbY+N/NTpTxKr 212 | ``` 213 | 214 | ## Safety 215 | 216 | This project is using [ring][4]'s `pbkdf2` function to derive a proper key from 217 | a password and its `aes256` implementation to encrypt/decrypt the secrets. In 218 | theory at least, those ciphers should not be brute-forceable. You can find an 219 | audit of the ring library by Cure53 [here][6]. 220 | 221 | With this being said, it is still important to protect the encrypted version of 222 | our secrets from being public. If you store your secrets alongside your code, 223 | that responsibility then befalls your code provider (github for example.) 224 | 225 | Ultimately, consider the following three characteristics: 226 | 227 | - Your profile (low-profile target, high-profile?) 228 | - Your threat-model (who do you accept to trust?) 229 | - Your compliance requirements (do you have [PII][5] data?) 230 | 231 | You need to consider all those questions (and more) before deciding on a 232 | solution for your secrets management. It is advised that you consult with a 233 | security engineer as well. 234 | 235 | [1]: https://crates.io 236 | [2]: https://crates.io/crates/tinytemplate 237 | [3]: https://docs.rs/tinytemplate/1.0.4/tinytemplate/syntax/index.html 238 | [4]: https://github.com/briansmith/ring 239 | [5]: https://en.wikipedia.org/wiki/Personal_data 240 | [6]: https://github.com/ctz/rustls/blob/master/audit/TLS-01-report.pdf 241 | [7]: https://docs.rs/hips 242 | -------------------------------------------------------------------------------- /src/backends.rs: -------------------------------------------------------------------------------- 1 | //! [`Backend`][1] trait implementations. 2 | //! 3 | //! [1]: ../trait.Backend.html 4 | 5 | use crate::prelude::*; 6 | 7 | /// Store secrets in yaml format. 8 | /// 9 | /// We store a list of encrypted secrets, each entry containing: 10 | /// 11 | /// - name 12 | /// - secret (encrypted, base64) 13 | /// - salt (base64) 14 | /// 15 | /// This `Backend` will be selected by the binary if the given database path ends with `.yaml`. 16 | pub struct YAML { 17 | path: PathBuf, 18 | } 19 | 20 | impl YAML { 21 | pub fn new(path: PathBuf) -> Self { 22 | Self { path } 23 | } 24 | } 25 | impl Backend for YAML { 26 | fn store(&mut self, encrypted: Encrypted) -> Result<()> { 27 | let mut secrets = self.read().context("loading database")?; 28 | if let Some(existing_pos) = secrets.iter().position(|s| s.name == encrypted.name) { 29 | secrets.remove(existing_pos); 30 | secrets.insert(existing_pos, encrypted); 31 | } else { 32 | secrets.push(encrypted); 33 | } 34 | self.write(secrets).context("writing database") 35 | } 36 | 37 | fn load(&self, name: String) -> Result { 38 | self.read().context("loading database")?.into_iter().find(|s| { 39 | s.name == name 40 | }).map(|s| Ok(s)).unwrap_or_else(|| Err(Error::msg("secret not found"))) 41 | } 42 | 43 | fn remove(&mut self, name: String) -> Result<()> { 44 | let secrets = self.read().context("loading database")?.into_iter().filter(|s| { 45 | s.name != name 46 | }).collect(); 47 | self.write(secrets) 48 | } 49 | 50 | fn list(&self) -> Result> { 51 | self.read() 52 | } 53 | } 54 | impl YAML { 55 | fn read(&self) -> Result> { 56 | Ok(::serde_yaml::from_str(&match ::std::fs::read_to_string(&self.path) { 57 | Err(err) if err.kind() == ::std::io::ErrorKind::NotFound => Ok("[]".to_string()), 58 | Err(err) => Err(err), 59 | Ok(val) => Ok(val), 60 | }.context("reading file")?).context("unmarshalling yaml")?) 61 | } 62 | 63 | fn write(&mut self, secrets: Vec) -> Result<()> { 64 | let mut f = ::std::fs::OpenOptions::new().write(true).create(true) 65 | .truncate(true).open(&self.path).context("opening file")?; 66 | Ok(f.write_all( 67 | ::serde_yaml::to_string(&secrets).context("marshalling to yaml")?.as_bytes() 68 | ).context("writing to file")?) 69 | } 70 | } 71 | 72 | /// Store the secrets in a directory hierarchy. 73 | /// 74 | /// The path points to the main folder, which is created by the library. A sub-folder named after 75 | /// the secret entry is created for each secret, with the following files inside it: 76 | /// 77 | /// - secret (encrypted, base64) 78 | /// - salt (base64) 79 | /// 80 | /// This `Backend` will be selected by the binary if the given database path has no extension. 81 | pub struct Folder { 82 | path: PathBuf, 83 | } 84 | impl Folder { 85 | pub fn new(path: PathBuf) -> Self { 86 | Self { path } 87 | } 88 | } 89 | impl Backend for Folder { 90 | fn store(&mut self, encrypted: Encrypted) -> Result<()> { 91 | self.ensure_root(&encrypted.name)?; 92 | 93 | let mut salt_f = ::std::fs::OpenOptions::new().write(true).create(true) 94 | .truncate(true).open(self.salt_path(&encrypted.name)).context("opening file")?; 95 | salt_f.write_all(encrypted.salt.as_bytes())?; 96 | 97 | let mut secret_f = ::std::fs::OpenOptions::new().write(true).create(true) 98 | .truncate(true).open(self.secret_path(&encrypted.name)).context("opening file")?; 99 | Ok(secret_f.write_all(encrypted.secret.as_bytes())?) 100 | } 101 | 102 | fn load(&self, name: String) -> Result { 103 | let salt_path = self.salt_path(&name); 104 | let secret_path = self.secret_path(&name); 105 | Ok(Encrypted{ 106 | name, 107 | secret: ::std::fs::read_to_string(secret_path).context("reading secret file")?, 108 | salt: ::std::fs::read_to_string(salt_path).context("reading salt file")?, 109 | }) 110 | } 111 | 112 | fn remove(&mut self, name: String) -> Result<()> { 113 | Ok(::std::fs::remove_dir_all(self.path.join(name))?) 114 | } 115 | 116 | fn list(&self) -> Result> { 117 | ::std::fs::read_dir(&self.path).context( 118 | "listing secret files" 119 | )?.collect::, _>>()?.into_iter().filter_map(|dir| { 120 | dir.path().file_name().map(|fname| self.load(fname.to_str().unwrap().to_owned())) 121 | }).collect::>>() 122 | } 123 | } 124 | impl Folder { 125 | fn ensure_root(&self, name: &str) -> Result { 126 | let root_path = self.path.join(name); 127 | let root_md = match ::std::fs::metadata(&root_path) { 128 | Err(err) if err.kind() == ::std::io::ErrorKind::NotFound => { 129 | ::std::fs::create_dir_all(&root_path)?; 130 | ::std::fs::metadata(&root_path)? 131 | } 132 | Err(err) => return Err(err.into()), 133 | Ok(md) => md, 134 | }; 135 | 136 | if root_md.is_dir() { 137 | Ok(root_path) 138 | } else { 139 | Err(Error::msg("secret path is invalid (should be a directory)")) 140 | } 141 | } 142 | 143 | fn salt_path(&self, name: &str) -> PathBuf { 144 | self.path.join(name).join("salt") 145 | } 146 | 147 | fn secret_path(&self, name: &str) -> PathBuf { 148 | self.path.join(name).join("secret") 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/commands.rs: -------------------------------------------------------------------------------- 1 | use ::clishe::prelude::*; 2 | use ::std::io::Write; 3 | 4 | commands! { 5 | #[clap(about = "Store provided secret under the provided name")] 6 | Store(self, db: &mut hips::Database) -> Result<()> { 7 | db.store(hips::Secret{name: self.name, secret: self.secret}) 8 | } struct { 9 | #[clap(help = "The name to store/hide the secret under")] 10 | name: String, 11 | #[clap(help = "The secret to store and hide")] 12 | secret: String, 13 | }, 14 | 15 | #[clap(about = "Retrieve secret under the provided name")] 16 | Load(self, db: &mut hips::Database) -> Result<()> { 17 | writeln!(::std::io::stdout(), "{}", db.load(self.name)?.secret)?; 18 | Ok(()) 19 | } struct { 20 | #[clap(help = "The name to retrieve the secrets for")] 21 | name: String, 22 | }, 23 | 24 | #[clap(alias = "ls", about = "List all available secrets")] 25 | List(self, db: &mut hips::Database) -> Result<()> { 26 | let mut names = db.list()?.into_iter().map(|secret| { 27 | secret.name 28 | }).collect::>(); 29 | names.sort(); 30 | writeln!(::std::io::stdout(), "{}", names.join("\n"))?; 31 | Ok(()) 32 | } struct {}, 33 | 34 | #[clap(alias = "rm", about = "Remove the secret under the provided name")] 35 | Remove(self, db: &mut hips::Database) -> Result<()> { 36 | db.remove(self.name) 37 | } struct { 38 | #[clap(help = "The name to retrieve the secrets for")] 39 | name: String, 40 | }, 41 | 42 | #[clap(about = "Rename the secret to the provided name")] 43 | Rename(self, db: &mut hips::Database) -> Result<()> { 44 | let secret = db.load(self.current_name.clone())?; 45 | db.store(hips::Secret{name: self.new_name, secret: secret.secret})?; 46 | db.remove(self.current_name) 47 | } struct { 48 | #[clap(help = "Current name of the secret to move")] 49 | current_name: String, 50 | #[clap(help = "New name of the secret")] 51 | new_name: String, 52 | }, 53 | 54 | #[clap(alias = "rot", about = "Re-encrypt the whole database using a new password")] 55 | Rotate(self, db: &mut hips::Database) -> Result<()> { 56 | let db_path = ::std::env::var("HIPS_DATABASE")?.into(); 57 | let mut new_db = hips::Database::from_file(db_path, self.new_password)?; 58 | for secret in db.list()? { 59 | new_db.store(secret)?; 60 | } 61 | Ok(()) 62 | } struct { 63 | #[clap(name = "new-password", help = "The password to re-encrypt the database with")] 64 | new_password: String, 65 | }, 66 | 67 | #[clap(alias = "tmp", about = "Print one or multiple secrets according to a template")] 68 | Template(self, db: &mut hips::Database) -> Result<()> { 69 | let template = match ::std::fs::read_to_string(&self.template) { 70 | Err(err) if err.kind() == ::std::io::ErrorKind::NotFound => Ok(self.template), 71 | Err(err) => Err(err), 72 | Ok(val) => Ok(val), 73 | }?; 74 | writeln!(::std::io::stdout(), "{}", db.template(template)?)?; 75 | Ok(()) 76 | } struct { 77 | #[clap(help = "Template or path to file containing the template")] 78 | template: String, 79 | }, 80 | } 81 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use ::std::convert::TryFrom; 3 | use ::std::iter::FromIterator; 4 | 5 | impl Database { 6 | /// Instantiate a new `Database` with injected `Backend`/`Encrypter`. 7 | pub fn new(backend: Box, encrypter: Box) -> Self { 8 | Self { 9 | b: backend, 10 | e: encrypter, 11 | } 12 | } 13 | 14 | /// Instantiate a new `Database` from a file. 15 | /// 16 | /// This function will try to guess which database type is needed, and create the appropriate 17 | /// backend based on that. This is used in the binary, which supports only the `Backend`s and 18 | /// `Encrypter`s shipped with hips. 19 | pub fn from_file(path: PathBuf, password: String) -> Result { 20 | if let Some(extension) = path.extension() { 21 | if extension == "yaml" { 22 | Ok(Self::new( 23 | Box::new(crate::backends::YAML::new(path)), 24 | Box::new(crate::encrypters::Ring::new(password)), 25 | )) 26 | } else { 27 | Err(Error::msg(format!( 28 | "unsupported format: {}", 29 | extension.to_str().unwrap() 30 | ))) 31 | } 32 | } else { 33 | Ok(Self::new( 34 | Box::new(crate::backends::Folder::new(path)), 35 | Box::new(crate::encrypters::Ring::new(password)), 36 | )) 37 | } 38 | } 39 | } 40 | 41 | impl Database { 42 | /// Store the provided secret. 43 | pub fn store(&mut self, secret: Secret) -> Result<()> { 44 | let encrypted = self.e.encrypt(secret).context("encrypting secret")?; 45 | self.b.store(encrypted).context("storing secret") 46 | } 47 | 48 | /// Load the `name` secret. 49 | pub fn load(&self, name: String) -> Result { 50 | self.e 51 | .decrypt(self.b.load(name).context("looking up name")?) 52 | .context("decrypting secret") 53 | } 54 | 55 | /// Remove the `name` secret. 56 | pub fn remove(&mut self, name: String) -> Result<()> { 57 | self.b.remove(name).context("removing secret") 58 | } 59 | 60 | /// List all secrets. 61 | pub fn list(&self) -> Result> { 62 | self.b 63 | .list() 64 | .context("listing secrets")? 65 | .into_iter() 66 | .map(|s| self.e.decrypt(s)) 67 | .collect::>>() 68 | } 69 | } 70 | 71 | impl Database { 72 | /// Process the database through a template. 73 | /// 74 | /// This call will read the provided `template` and replace all references to secrets with the 75 | /// secrets stored in the database. We use the [tinytemplate][1] engine, see their [syntax 76 | /// page][2] for more context. 77 | /// 78 | /// [1]: https://crates.io/crates/tinytemplate 79 | /// [2]: https://docs.rs/tinytemplate/1.0.4/tinytemplate/syntax/index.html 80 | pub fn template(&self, mut template: String) -> Result { 81 | template = ::snailquote::unescape(&format!("\"{}\"", template))?; 82 | 83 | let mut tt = ::tinytemplate::TinyTemplate::new(); 84 | tt.add_template("template", &template)?; 85 | tt.add_formatter("capitalize", |val, s| match val { 86 | ::serde_json::Value::String(string) => { 87 | s.push_str(&string.to_uppercase()); 88 | Ok(()) 89 | } 90 | _ => panic!("can only capitalize strings"), 91 | }); 92 | 93 | let tctx = TemplateContext::try_from(self)?; 94 | Ok(tt.render("template", &tctx)?) 95 | } 96 | } 97 | 98 | #[derive(Serialize)] 99 | struct TemplateContext { 100 | list: Vec, 101 | map: ::std::collections::HashMap, 102 | } 103 | 104 | impl ::std::convert::TryFrom<&Database> for TemplateContext { 105 | type Error = Error; 106 | fn try_from(db: &Database) -> Result { 107 | let secrets = db.list()?; 108 | Ok(Self { 109 | list: secrets.clone(), 110 | map: ::std::collections::HashMap::from_iter( 111 | secrets 112 | .into_iter() 113 | .map(|secret| (secret.name, secret.secret)), 114 | ), 115 | }) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/encrypters.rs: -------------------------------------------------------------------------------- 1 | //! [`Encrypter`][1] trait implementations. 2 | //! 3 | //! [1]: ../trait.Encrypter.html 4 | 5 | use crate::prelude::*; 6 | use ring::rand::SecureRandom; 7 | use std::convert::TryInto; 8 | 9 | const IV_SIZE: usize = 12; 10 | const SALT_SIZE: usize = 32; 11 | const TAG_SIZE: usize = 16; 12 | const KEY_LEN: usize = 32; 13 | const ITERATIONS: usize = 100_000; 14 | 15 | /// Encrypt using the [ring][1] library. 16 | /// 17 | /// The key is generated from the password provided at initialization using the PBKDF2 scheme. 18 | /// The cipher we use is AES256 GCM with 100,000 iterations. 19 | /// 20 | /// [1]: https://github.com/briansmith/ring 21 | pub struct Ring(String); 22 | 23 | impl Ring { 24 | /// Instantiate a new `Encrypter` based on the ring library. 25 | pub fn new(password: String) -> Self { 26 | Self(password) 27 | } 28 | 29 | fn key(&self, salt: &[u8]) -> Result> { 30 | let mut key = [0; KEY_LEN]; 31 | ::ring::pbkdf2::derive( 32 | ::ring::pbkdf2::PBKDF2_HMAC_SHA256, 33 | ::core::num::NonZeroU32::new(ITERATIONS as u32).expect("iterations not 0"), 34 | &salt, 35 | self.0.as_bytes(), 36 | &mut key, 37 | ); 38 | Ok(key.to_vec()) 39 | } 40 | } 41 | 42 | impl Encrypter for Ring { 43 | fn encrypt(&self, secret: Secret) -> Result { 44 | assert_eq!(::ring::aead::AES_256_GCM.tag_len(), TAG_SIZE); 45 | 46 | let plaintext = ::ring::aead::Aad::empty(); 47 | let mut ciphertext = secret.secret.as_bytes().to_vec(); 48 | for _ in 0..::ring::aead::AES_256_GCM.tag_len() { 49 | ciphertext.push(0); 50 | } 51 | 52 | let rand = ::ring::rand::SystemRandom::new(); 53 | let mut salt = vec![0u8; SALT_SIZE]; 54 | rand.fill(&mut salt) 55 | .map_err(|err| Error::msg(format!("{}", err)))?; 56 | let mut iv = [0u8; IV_SIZE]; 57 | rand.fill(&mut iv) 58 | .map_err(|err| Error::msg(format!("{}", err)))?; 59 | let nonce = ::ring::aead::Nonce::assume_unique_for_key(iv); 60 | 61 | let key = self.key(&salt)?; 62 | let key = ::ring::aead::UnboundKey::new(&::ring::aead::AES_256_GCM, &key[..]) 63 | .map_err(|err| Error::msg(err.to_string()))?; 64 | let key = ::ring::aead::LessSafeKey::new(key); 65 | 66 | key.seal_in_place_append_tag(nonce, plaintext, &mut ciphertext) 67 | .map_err(|err| Error::msg(err.to_string()))?; 68 | 69 | let ciphertext = ::base64::encode( 70 | &iv.iter() 71 | .chain(ciphertext.iter()) 72 | .map(|v| *v) 73 | .collect::>(), 74 | ); 75 | let salt = ::base64::encode(&salt); 76 | 77 | Ok(Encrypted { 78 | name: secret.name, 79 | secret: ciphertext, 80 | salt, 81 | }) 82 | } 83 | 84 | fn decrypt(&self, encrypted: Encrypted) -> Result { 85 | let plaintext = ::ring::aead::Aad::empty(); 86 | let mut ciphertext = ::base64::decode(&encrypted.secret).context("decoding ciphertext")?; 87 | 88 | let iv = &ciphertext[..IV_SIZE]; 89 | let nonce = 90 | ::ring::aead::Nonce::assume_unique_for_key(iv.try_into().context("transforming iv")?); 91 | let mut ciphertext = &mut ciphertext[IV_SIZE..]; 92 | let salt = ::base64::decode(&encrypted.salt).context("decoding salt")?; 93 | 94 | let key = self.key(&salt).context("computing key")?; 95 | let key = ::ring::aead::UnboundKey::new(&::ring::aead::AES_256_GCM, &key[..]) 96 | .map_err(|err| Error::msg(err.to_string())) 97 | .context("generating unbound key")?; 98 | let key = ::ring::aead::LessSafeKey::new(key); 99 | 100 | let secret = key 101 | .open_in_place(nonce, plaintext, &mut ciphertext) 102 | .map_err(|err| Error::msg(err.to_string())) 103 | .context("running aes_256_gcm")?; 104 | 105 | Ok(Secret { 106 | name: encrypted.name, 107 | secret: ::std::str::from_utf8(&secret[..(secret.len() - TAG_SIZE)]) 108 | .context("loading as utf8")? 109 | .to_owned(), 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A simple secrets database. 2 | //! 3 | //! While `hips` can be used as a binary for operational purposes, you might need your server to 4 | //! retrieve secrets at runtime. 5 | //! 6 | //! You should be mostly manipulating the [`Database`][1] object directly. It exposes an API very 7 | //! similar to the hips binary's (store, load, remove, list...) 8 | //! 9 | //! [1]: struct.Database.html 10 | 11 | #[macro_use] 12 | extern crate serde; 13 | 14 | use crate::prelude::*; 15 | mod prelude { 16 | pub use crate::{Backend, Database, Encrypter}; 17 | pub use crate::{Encrypted, Secret}; 18 | pub use anyhow::{Context, Error, Result}; 19 | pub use std::io::{Read, Write}; 20 | pub use std::path::PathBuf; 21 | } 22 | 23 | /// A handle to the underlying secrets database. 24 | /// 25 | /// Any calls to this object's methods (load, store..) will result in a similar call on the 26 | /// injected `Backend` implementation, no caching is being done. Every call will also invoke 27 | /// encryption/decryption logic, which should be considered slow. 28 | pub struct Database { 29 | b: Box, 30 | e: Box, 31 | } 32 | mod database; 33 | 34 | /// Storage behavior: what does it mean to store/load/..? 35 | /// 36 | /// A few backends are implemented by default ([`YAML`][1] and [`Folder`][2] at this time.) You are 37 | /// free to implement your own `Backend` (that connects to a remote server for example) and 38 | /// initialize a new `Database` with it. 39 | /// 40 | /// [1]: backends/struct.YAML.html 41 | /// [2]: backends/struct.Folder.html 42 | pub trait Backend { 43 | fn store(&mut self, encrypted: Encrypted) -> Result<()>; 44 | fn load(&self, name: String) -> Result; 45 | fn remove(&mut self, name: String) -> Result<()>; 46 | fn list(&self) -> Result>; 47 | } 48 | pub mod backends; 49 | 50 | /// Encryption behavior: what does it mean to encrypt/decrypt? 51 | /// 52 | /// A single [`Ring`][1] encrypter is available at this time. In a past version, an openssl option 53 | /// was also available. You are free to implement your own `Encrypter` and initialize a new 54 | /// `Database` with it. 55 | /// 56 | /// [1]: encrypters/struct.Ring.html 57 | pub trait Encrypter { 58 | fn encrypt(&self, secret: Secret) -> Result; 59 | fn decrypt(&self, encrypted: Encrypted) -> Result; 60 | } 61 | pub mod encrypters; 62 | 63 | /// A plaintext secret and its name. 64 | /// 65 | /// Returned by the `decrypt` method of an [`Encrypter`][1] when provided an [`Encrypted`][2] 66 | /// secret. Passing this secret to the same encrypter's `encrypt` method again might yield 67 | /// different `Encrypted` data (this depends on the encrypter implementation.) 68 | /// 69 | /// [1]: trait.Encrypter.html 70 | /// [2]: struct.Encrypted.html 71 | #[derive(Clone, Debug, Serialize, Deserialize)] 72 | pub struct Secret { 73 | pub name: String, 74 | pub secret: String, 75 | } 76 | 77 | /// An encrypted secret and its name. 78 | /// 79 | /// Returned by the `encrypt` method of an [`Encrypter`][1] when provided a [`Secret`][2]. Should 80 | /// yield the same `Secret` when passed to the same encrypter's `decrypt` method. 81 | /// 82 | /// [1]: trait.Encrypter.html 83 | /// [2]: struct.Secret.html 84 | #[derive(Clone, Debug, Serialize, Deserialize)] 85 | pub struct Encrypted { 86 | name: String, 87 | secret: String, 88 | salt: String, 89 | } 90 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate clap; 3 | 4 | use ::clap::Parser; 5 | use ::clishe::prelude::*; 6 | use ::std::io::Write; 7 | 8 | fn main() -> Result<()> { 9 | if let Err(err) = run() { 10 | writeln!(::std::io::stderr(), "error: {:#}", err)?; 11 | ::std::process::exit(1); 12 | } 13 | Ok(()) 14 | } 15 | 16 | fn run() -> Result<()> { 17 | let db_path = unwrap_env_var("HIPS_DATABASE")?.into(); 18 | let password = unwrap_env_var("HIPS_PASSWORD")?; 19 | Hips::parse().run(&mut ::hips::Database::from_file(db_path, password)?) 20 | } 21 | 22 | dispatchers! { 23 | #[clap( 24 | name = env!("CARGO_PKG_NAME"), 25 | about = env!("CARGO_PKG_DESCRIPTION"), 26 | author = env!("CARGO_PKG_AUTHORS"), 27 | version = env!("CARGO_PKG_VERSION"), 28 | after_help = "\ 29 | ENVIRONMENT:\n \ 30 | HIPS_DATABASE File/folder containing the secrets (mandatory)\n \ 31 | HIPS_PASSWORD Password that will unlock the database (mandatory)\ 32 | ", 33 | )] 34 | Hips(self, _: &mut hips::Database) -> Result<()> [ 35 | Store: commands::Store, 36 | Load: commands::Load, 37 | List: commands::List, 38 | Remove: commands::Remove, 39 | Rename: commands::Rename, 40 | Rotate: commands::Rotate, 41 | Template: commands::Template, 42 | ], 43 | } 44 | mod commands; 45 | 46 | fn unwrap_env_var(name: &str) -> Result { 47 | let var = ::std::env::var(name).map_err(|err| Error::msg(format!("{}: {}", err, name))); 48 | 49 | if var.is_err() { 50 | eprintln!("hips expects both a database file/folder and its"); 51 | eprintln!("password to be provided as environment variables"); 52 | } 53 | 54 | var 55 | } 56 | --------------------------------------------------------------------------------