Skip to content

Commit 1bafb0c

Browse files
authored
Add sign command (#66)
1 parent 9805518 commit 1bafb0c

18 files changed

Lines changed: 365 additions & 50 deletions

bin/version

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/\1/p' Cargo.toml | head -1

justfile

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ publish: tmp
3838
set -euxo pipefail
3939
git clone [email protected]:casey/filepack.git tmp
4040
cd tmp
41-
VERSION=`sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/\1/p' Cargo.toml | head -1`
41+
VERSION=`bin/version`
4242
git tag -a $VERSION -m "Release $VERSION"
4343
git push origin $VERSION
4444
cargo publish
@@ -74,13 +74,34 @@ test-progress-bar: tmp
7474
cargo run --release create tmp
7575
rm tmp/data*
7676

77-
download-release: tmp
78-
#!/usr/bin/env bash
79-
VERSION=`sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/\1/p' Cargo.toml | head -1`
80-
cd tmp
81-
cargo run download --github-release casey/filepack/$VERSION
82-
8377
check-error-variant-order: tmp
8478
cat src/error.rs | rg '^ ([A-Z].*) \{' -or '$1' > tmp/original.txt
8579
sort tmp/original.txt > tmp/sorted.txt
8680
diff tmp/{original,sorted}.txt
81+
82+
sign-release: tmp
83+
#!/usr/bin/env bash
84+
set -euxo pipefail
85+
VERSION=`bin/version`
86+
gh release download \
87+
--repo casey/filepack \
88+
--pattern filepack.json \
89+
--dir tmp \
90+
$VERSION
91+
cargo run sign tmp/filepack.json
92+
gh release upload \
93+
--clobber \
94+
--repo casey/filepack \
95+
$VERSION \
96+
tmp/filepack.json
97+
98+
verify-release: tmp
99+
#!/usr/bin/env bash
100+
set -euxo pipefail
101+
VERSION=`bin/version`
102+
gh release download \
103+
--repo casey/filepack \
104+
--pattern '*' \
105+
--dir tmp \
106+
$VERSION
107+
cargo run verify tmp --key `cargo run key`

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ pub(crate) enum Error {
129129
},
130130
#[snafu(display("root hash mismatch"))]
131131
RootHashMismatch { backtrace: Option<Backtrace> },
132+
#[snafu(display("manifest has already been signed by public key `{public_key}`"))]
133+
SignatureAlreadyExists {
134+
backtrace: Option<Backtrace>,
135+
public_key: PublicKey,
136+
},
132137
#[snafu(display("invalid signature for public key `{public_key}`"))]
133138
SignatureInvalid {
134139
backtrace: Option<Backtrace>,

src/manifest.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ impl Manifest {
2424

2525
hasher.finalize().into()
2626
}
27+
28+
pub(crate) fn to_json(&self) -> String {
29+
serde_json::to_string(self).unwrap()
30+
}
2731
}
2832

2933
#[cfg(test)]

src/metadata.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ pub(crate) struct Metadata {
88

99
impl Metadata {
1010
pub(crate) const FILENAME: &'static str = "metadata.json";
11+
12+
pub(crate) fn to_json(&self) -> String {
13+
serde_json::to_string(self).unwrap()
14+
}
1115
}
1216

1317
impl From<Template> for Metadata {

src/private_key.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ impl PrivateKey {
5252
Ok(private_key)
5353
}
5454

55+
pub(crate) fn load_and_sign(path: &Utf8Path, message: &[u8]) -> Result<(PublicKey, Signature)> {
56+
let private_key = Self::load(path)?;
57+
58+
let signature = private_key.sign(message);
59+
60+
Ok((private_key.public_key(), signature))
61+
}
62+
5563
pub(crate) fn public_key(&self) -> PublicKey {
5664
self.clone().into()
5765
}

src/public_key.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ impl PublicKey {
4242
Ok(public_key)
4343
}
4444

45-
pub(crate) fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
45+
pub(crate) fn verify(&self, message: &[u8], signature: &Signature) -> Result<()> {
4646
self
4747
.0
4848
.verify_strict(message, signature.as_ref())
4949
.map_err(SignatureError)
50+
.context(error::SignatureInvalid {
51+
public_key: self.clone(),
52+
})
5053
}
5154
}
5255

src/subcommand.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod hash;
1111
mod key;
1212
mod keygen;
1313
mod man;
14+
mod sign;
1415
mod verify;
1516

1617
#[derive(Parser)]
@@ -34,6 +35,8 @@ pub(crate) enum Subcommand {
3435
Keygen,
3536
#[command(about = "Print man page")]
3637
Man,
38+
#[command(about = "Add signature to manifest")]
39+
Sign(sign::Sign),
3740
#[command(about = "Verify manifest")]
3841
Verify(verify::Verify),
3942
}
@@ -46,6 +49,7 @@ impl Subcommand {
4649
Self::Key => key::run(options),
4750
Self::Keygen => keygen::run(options),
4851
Self::Man => man::run(),
52+
Self::Sign(sign) => sign.run(options),
4953
Self::Verify(verify) => verify.run(options),
5054
}
5155
}

src/subcommand/create.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl Create {
4141
error::MetadataAlreadyExists { path: &path },
4242
}
4343
let metadata = Metadata::from(template);
44-
let json = serde_json::to_string(&metadata).unwrap();
44+
let json = metadata.to_json();
4545
fs::write(&path, json).context(error::Io { path: &path })?;
4646
}
4747

@@ -181,18 +181,13 @@ impl Create {
181181
if self.sign {
182182
let private_key_path = options.key_dir()?.join(MASTER_PRIVATE_KEY);
183183

184-
let private_key = PrivateKey::load(&private_key_path)?;
185-
186-
let signature = private_key.sign(manifest.root_hash().as_bytes());
187-
188-
let public_key = private_key.public_key();
184+
let (public_key, signature) =
185+
PrivateKey::load_and_sign(&private_key_path, manifest.root_hash().as_bytes())?;
189186

190187
manifest.signatures.insert(public_key, signature);
191188
}
192189

193-
let json = serde_json::to_string(&manifest).unwrap();
194-
195-
fs::write(&manifest_path, &json).context(error::Io {
190+
fs::write(&manifest_path, manifest.to_json()).context(error::Io {
196191
path: manifest_path,
197192
})?;
198193

src/subcommand/hash.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ use super::*;
22

33
#[derive(Parser)]
44
pub(crate) struct Hash {
5-
#[arg(help = "Hash file at <PATH>, defaulting to standard input")]
6-
path: Option<Utf8PathBuf>,
5+
#[arg(help = "Hash <FILE>, defaulting to standard input")]
6+
file: Option<Utf8PathBuf>,
77
}
88

99
impl Hash {
1010
pub(crate) fn run(self, options: Options) -> Result {
11-
let hash = if let Some(path) = self.path {
11+
let hash = if let Some(path) = self.file {
1212
options.hash_file(&path).context(error::Io { path })?.hash
1313
} else {
1414
let mut hasher = Hasher::new();

0 commit comments

Comments
 (0)