radicle-crypto: split out from radicle

The crypto types are useful to use in other components that are
separate from the radicle crate, e.g. the incoming radicle-cobs
crate.

Separate out the types into a radicle-crypto crate. This crate defines
the usual PublicKey, SecretKey, and Signature types. It also provides
the ssh functionality, under the `ssh` feature. It is also necessary
to provide the `From` impl for `Component` here, which again is
guarded by the `git-ref-format` feature.

The `Arbitrary` implementation are moved. The arbitrary decision of
redefining `ByteArray` in both radicle-crypto and radicle was made.

Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
Fintan Halpenny 2022-10-27 16:56:50 +01:00
parent 2a1154ef87
commit c0b0d29e19
No known key found for this signature in database
GPG Key ID: 2552FB6F64066CB7
29 changed files with 210 additions and 80 deletions

21
Cargo.lock generated
View File

@ -1092,6 +1092,7 @@ dependencies = [
"once_cell",
"quickcheck",
"quickcheck_macros",
"radicle-crypto",
"radicle-git-ext",
"radicle-ssh",
"serde",
@ -1104,6 +1105,25 @@ dependencies = [
"zeroize",
]
[[package]]
name = "radicle-crypto"
version = "0.1.0"
dependencies = [
"base64",
"ed25519-compact",
"fastrand",
"git-ref-format",
"multibase",
"quickcheck",
"quickcheck_macros",
"radicle-ssh",
"serde",
"sha2 0.10.6",
"sqlite",
"thiserror",
"zeroize",
]
[[package]]
name = "radicle-git-ext"
version = "0.1.0"
@ -1171,6 +1191,7 @@ name = "radicle-remote-helper"
version = "0.2.0"
dependencies = [
"radicle",
"radicle-crypto",
"thiserror",
]

View File

@ -1,6 +1,7 @@
[workspace]
members = [
"radicle",
"radicle-crypto",
"radicle-node",
"radicle-tools",
"radicle-ssh",

55
radicle-crypto/Cargo.toml Normal file
View File

@ -0,0 +1,55 @@
[package]
name = "radicle-crypto"
license = "MIT OR Apache-2.0"
version = "0.1.0"
authors = [
"Alexis Sellier <alexis@radicle.xyz>",
"Fintan Halpenny <fintan.halpenny@gmail.com>",
]
edition = "2021"
[features]
test = ["fastrand", "quickcheck"]
ssh = ["base64", "radicle-ssh", "sha2"]
[dependencies]
ed25519-compact = { version = "1.0.12", features = ["pem"] }
multibase = { version = "0.9.1" }
serde = { version = "1", features = ["derive"] }
sqlite = { version = "0.27.0", optional = true }
thiserror = { version = "1" }
[dependencies.fastrand]
version = "1.8.0"
default-features = false
optional = true
[dependencies.git-ref-format]
version = "0.1"
optional = true
[dependencies.quickcheck]
version = "1"
default-features = false
optional = true
[dependencies.radicle-ssh]
path = "../radicle-ssh"
version = "0"
default-features = false
optional = true
[dependencies.sha2]
version = "0.10.2"
optional = true
[dependencies.base64]
version = "0.13"
optional = true
[dev-dependencies]
fastrand = { version = "1.8.0" }
quickcheck_macros = { version = "1", default-features = false }
quickcheck = { version = "1", default-features = false }
zeroize = { version = "1.5.7" }

View File

@ -7,6 +7,12 @@ use thiserror::Error;
pub use ed25519::{Error, KeyPair, Seed};
#[cfg(any(test, feature = "test"))]
pub mod test;
#[cfg(feature = "ssh")]
pub mod ssh;
/// Verified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Verified;
@ -250,9 +256,37 @@ impl Deref for PublicKey {
}
}
#[cfg(feature = "git-ref-format")]
impl<'a> From<&PublicKey> for git_ref_format::Component<'a> {
fn from(id: &PublicKey) -> Self {
use git_ref_format::{Component, RefString};
let refstr =
RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
Component::from_refstring(refstr).expect("encoded public keys are valid refname components")
}
}
#[cfg(feature = "sqlite")]
impl sqlite::ValueInto for PublicKey {
fn into(value: &sqlite::Value) -> Option<Self> {
use sqlite::Value;
match value {
Value::String(id) => PublicKey::from_str(id).ok(),
_ => None,
}
}
}
#[cfg(feature = "sqlite")]
impl sqlite::Bindable for &PublicKey {
fn bind(self, stmt: &mut sqlite::Statement<'_>, i: usize) -> sqlite::Result<()> {
self.to_human().as_str().bind(stmt, i)
}
}
#[cfg(test)]
mod test {
use crate::crypto::PublicKey;
mod tests {
use crate::PublicKey;
use quickcheck_macros::quickcheck;
use std::str::FromStr;

View File

@ -9,13 +9,13 @@ use radicle_ssh::encoding::Encodable;
use radicle_ssh::encoding::Encoding;
use radicle_ssh::encoding::Reader;
use crate::crypto;
use crate::crypto::PublicKey;
use crate as crypto;
use crate::PublicKey;
pub mod fmt {
use radicle_ssh::encoding::Encoding as _;
use crate::crypto::PublicKey;
use crate::PublicKey;
/// Get the SSH long key from a public key.
/// This is the output of `ssh-add -L`.
@ -49,7 +49,7 @@ pub mod fmt {
use std::str::FromStr;
use super::*;
use crate::crypto::PublicKey;
use crate::PublicKey;
#[test]
fn test_key() {
@ -319,9 +319,9 @@ mod test {
use zeroize::Zeroizing;
use super::{fmt, ExtendedSignature, SecretKey};
use crate::crypto::PublicKey;
use crate::crypto::{self};
use crate as crypto;
use crate::test::arbitrary::ByteArray;
use crate::PublicKey;
use radicle_ssh::agent::client::{AgentClient, ClientStream, Error};
use radicle_ssh::encoding::*;

View File

@ -1,7 +1,7 @@
use radicle_ssh::agent::client::AgentClient;
use radicle_ssh::{self as ssh, agent::client::ClientStream};
use crate::crypto;
use crate as crypto;
use crate::ssh::SecretKey;
#[cfg(not(unix))]

View File

@ -0,0 +1,2 @@
pub mod arbitrary;
pub mod signer;

View File

@ -0,0 +1,46 @@
use quickcheck::Arbitrary;
use crate::{test::signer::MockSigner, KeyPair, PublicKey, Seed};
impl Arbitrary for MockSigner {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
let seed = Seed::new(bytes.into_inner());
let sk = KeyPair::from_seed(seed).sk;
MockSigner::from(sk)
}
}
impl Arbitrary for PublicKey {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
let seed = Seed::new(bytes.into_inner());
let keypair = KeyPair::from_seed(seed);
PublicKey(keypair.pk)
}
}
#[derive(Clone, Debug)]
pub struct ByteArray<const N: usize>([u8; N]);
impl<const N: usize> ByteArray<N> {
pub fn into_inner(self) -> [u8; N] {
self.0
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
}
impl<const N: usize> Arbitrary for ByteArray<N> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let mut bytes: [u8; N] = [0; N];
for byte in &mut bytes {
*byte = u8::arbitrary(g);
}
Self(bytes)
}
}

View File

@ -1,4 +1,4 @@
use crate::crypto::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer};
use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer};
#[derive(Debug, Clone)]
pub struct MockSigner {

View File

@ -432,7 +432,7 @@ mod tests {
use super::*;
use quickcheck_macros::quickcheck;
use crate::test::signer::MockSigner;
use crate::crypto::test::signer::MockSigner;
#[quickcheck]
fn prop_refs_announcement_signing(id: Id, refs: Refs) {

View File

@ -1,4 +1,4 @@
use radicle::test::signer::MockSigner;
use radicle::crypto::test::signer::MockSigner;
use crate::test::arbitrary;
use crate::{

View File

@ -8,6 +8,7 @@ use log::*;
use crate::address;
use crate::clock::{RefClock, Timestamp};
use crate::crypto::test::signer::MockSigner;
use crate::crypto::Signer;
use crate::identity::Id;
use crate::node;
@ -20,7 +21,6 @@ use crate::service::*;
use crate::storage::git::transport::remote;
use crate::storage::{RemoteId, WriteStorage};
use crate::test::arbitrary;
use crate::test::signer::MockSigner;
use crate::test::simulator;
use crate::{Link, LocalDuration, LocalTime};

View File

@ -5,6 +5,7 @@ use crossbeam_channel as chan;
use nakamoto_net as nakamoto;
use crate::collections::{HashMap, HashSet};
use crate::crypto::test::signer::MockSigner;
use crate::prelude::{LocalDuration, Timestamp};
use crate::service::config::*;
use crate::service::filter::Filter;
@ -22,7 +23,6 @@ use crate::test::fixtures;
#[allow(unused)]
use crate::test::logger;
use crate::test::peer::Peer;
use crate::test::signer::MockSigner;
use crate::test::simulator;
use crate::test::simulator::{Peer as _, Simulation};
use crate::test::storage::MockStorage;

View File

@ -12,6 +12,10 @@ thiserror = "1"
path = "../radicle"
version = "0"
[dependencies.radicle-crypto]
path = "../radicle-crypto"
version = "0"
[[bin]]
name = "git-remote-rad"
path = "src/git-remote-rad.rs"

View File

@ -4,9 +4,9 @@ use std::{env, io, process};
use thiserror::Error;
use radicle::crypto::ssh;
use radicle::crypto::{PublicKey, Signer};
use radicle::node::Handle;
use radicle::ssh;
use radicle::storage::git::transport::local::{Url, UrlError};
use radicle::storage::{ReadRepository, WriteRepository, WriteStorage};

View File

@ -1,4 +1,4 @@
use radicle::{crypto, ssh};
use radicle::{crypto, crypto::ssh};
use std::io::prelude::*;
use std::{env, io};

View File

@ -1,6 +1,6 @@
fn main() -> anyhow::Result<()> {
let keypair = radicle::crypto::KeyPair::generate();
radicle::ssh::agent::register(&keypair.sk)?;
radicle::crypto::ssh::agent::register(&keypair.sk)?;
let profile = radicle::Profile::init(keypair)?;

View File

@ -2,10 +2,10 @@ fn main() -> anyhow::Result<()> {
let profile = radicle::Profile::load()?;
println!("id: {}", profile.id());
println!("key: {}", radicle::ssh::fmt::key(profile.id()));
println!("key: {}", radicle::crypto::ssh::fmt::key(profile.id()));
println!(
"fingerprint: {}",
radicle::ssh::fmt::fingerprint(profile.id())
radicle::crypto::ssh::fmt::fingerprint(profile.id())
);
println!("home: {}", profile.home.display());

View File

@ -38,6 +38,11 @@ version = "0.15.0"
default-features = false
features = ["vendored-libgit2"]
[dependencies.radicle-crypto]
path = "../radicle-crypto"
version = "0"
features = ["git-ref-format", "ssh", "sqlite"]
[dependencies.radicle-ssh]
path = "../radicle-ssh"
version = "0"
@ -51,3 +56,8 @@ optional = true
[dev-dependencies]
quickcheck_macros = { version = "1", default-features = false }
quickcheck = { version = "1", default-features = false }
[dev-dependencies.radicle-crypto]
path = "../radicle-crypto"
version = "0"
features = ["test"]

View File

@ -57,14 +57,6 @@ pub enum ListRefsError {
InvalidRef(#[from] RefError),
}
impl<'a> From<&RemoteId> for Component<'a> {
fn from(id: &RemoteId) -> Self {
let refstr =
RefString::try_from(id.to_string()).expect("encoded public keys are valid ref strings");
Component::from_refstring(refstr).expect("encoded public keys are valid refname components")
}
}
pub mod refs {
use super::*;

View File

@ -488,13 +488,14 @@ impl Identity<Untrusted> {
#[cfg(test)]
mod test {
use crate::crypto::Signer;
use radicle_crypto::test::signer::MockSigner;
use radicle_crypto::Signer;
use crate::rad;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, WriteStorage};
use crate::test::arbitrary;
use crate::test::fixtures;
use crate::test::signer::MockSigner;
use super::*;
use quickcheck_macros::quickcheck;

View File

@ -1,7 +1,9 @@
#![allow(clippy::match_like_matches_macro)]
#![cfg_attr(not(test), warn(clippy::unwrap_used))]
pub extern crate radicle_crypto as crypto;
pub mod collections;
pub mod crypto;
pub mod git;
pub mod hash;
pub mod identity;
@ -12,7 +14,6 @@ pub mod rad;
pub mod serde_ext;
#[cfg(feature = "sql")]
pub mod sql;
pub mod ssh;
pub mod storage;
#[cfg(any(test, feature = "test"))]
pub mod test;

View File

@ -321,13 +321,15 @@ pub fn remote(repo: &git2::Repository) -> Result<(git2::Remote<'_>, Id), RemoteE
mod tests {
use std::collections::HashMap;
use radicle_crypto::test::signer::MockSigner;
use super::*;
use crate::git::fmt::refname;
use crate::identity::{Delegate, Did};
use crate::storage::git::transport;
use crate::storage::git::Storage;
use crate::storage::{ReadStorage, WriteStorage};
use crate::test::{fixtures, signer::MockSigner};
use crate::test::fixtures;
#[test]
fn test_init() {

View File

@ -4,7 +4,6 @@ use std::str::FromStr;
use sqlite as sql;
use sqlite::Value;
use crate::crypto::PublicKey;
use crate::identity::Id;
use crate::node;
@ -23,21 +22,6 @@ impl sqlite::Bindable for &Id {
}
}
impl sql::ValueInto for PublicKey {
fn into(value: &Value) -> Option<Self> {
match value {
Value::String(id) => PublicKey::from_str(id).ok(),
_ => None,
}
}
}
impl sqlite::Bindable for &PublicKey {
fn bind(self, stmt: &mut sql::Statement<'_>, i: usize) -> sql::Result<()> {
self.to_human().as_str().bind(stmt, i)
}
}
impl sql::Bindable for node::Features {
fn bind(self, stmt: &mut sql::Statement<'_>, i: usize) -> sql::Result<()> {
(*self.deref() as i64).bind(stmt, i)

View File

@ -8,12 +8,11 @@ use std::{fmt, io};
use thiserror::Error;
use crypto::{PublicKey, Signer, Unverified, Verified};
pub use git::{ProjectError, VerifyError};
pub use radicle_git_ext::Oid;
use crate::collections::HashMap;
use crate::crypto;
use crate::crypto::{PublicKey, Signer, Unverified, Verified};
use crate::git::ext as git_ext;
use crate::git::{Qualified, RefError, RefString};
use crate::identity;

View File

@ -4,10 +4,10 @@ use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use std::{fs, io};
use crypto::{Signer, Unverified, Verified};
use git_ref_format::refspec;
use once_cell::sync::Lazy;
use crate::crypto::{Signer, Unverified, Verified};
use crate::git;
use crate::identity;
use crate::identity::project::{Identity, IdentityError};
@ -664,8 +664,8 @@ pub mod trailers {
use std::str::FromStr;
use super::*;
use crate::crypto::{PublicKey, PublicKeyError};
use crate::crypto::{Signature, SignatureError};
use crypto::{PublicKey, PublicKeyError};
use crypto::{Signature, SignatureError};
pub const SIGNATURE_TRAILER: &str = "Rad-Signature";
@ -716,6 +716,8 @@ mod tests {
use std::io::{Read, Write};
use std::{io, net, process, thread};
use crypto::test::signer::MockSigner;
use super::*;
use crate::assert_matches;
use crate::git;
@ -724,7 +726,6 @@ mod tests {
use crate::storage::{ReadRepository, ReadStorage, RefUpdate, WriteRepository};
use crate::test::arbitrary;
use crate::test::fixtures;
use crate::test::signer::MockSigner;
#[test]
fn test_remote_refs() {

View File

@ -7,12 +7,11 @@ use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::str::FromStr;
use crypto::{PublicKey, Signature, Signer, Unverified, Verified};
use once_cell::sync::Lazy;
use radicle_git_ext as git_ext;
use thiserror::Error;
use crate::crypto;
use crate::crypto::{PublicKey, Signature, Signer, Unverified, Verified};
use crate::git;
use crate::git::Oid;
use crate::storage;

View File

@ -2,5 +2,4 @@
pub mod arbitrary;
pub mod assert;
pub mod fixtures;
pub mod signer;
pub mod storage;

View File

@ -3,18 +3,17 @@ use std::hash::Hash;
use std::iter;
use std::ops::RangeBounds;
use crypto::test::signer::MockSigner;
use crypto::{PublicKey, Signer, Unverified, Verified};
use nonempty::NonEmpty;
use quickcheck::Arbitrary;
use crate::collections::HashMap;
use crate::crypto;
use crate::crypto::{KeyPair, PublicKey, Seed, Signer, Unverified, Verified};
use crate::git;
use crate::hash;
use crate::identity::{project::Delegate, project::Doc, Did, Id};
use crate::storage;
use crate::storage::refs::{Refs, SignedRefs};
use crate::test::signer::MockSigner;
use crate::test::storage::MockStorage;
pub fn set<T: Eq + Hash + Arbitrary>(range: impl RangeBounds<usize>) -> HashSet<T> {
@ -174,16 +173,6 @@ impl Arbitrary for Refs {
}
}
impl Arbitrary for MockSigner {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
let seed = Seed::new(bytes.into_inner());
let sk = KeyPair::from_seed(seed).sk;
MockSigner::from(sk)
}
}
impl Arbitrary for MockStorage {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let inventory = Arbitrary::arbitrary(g);
@ -216,13 +205,3 @@ impl Arbitrary for hash::Digest {
hash::Digest::new(&bytes)
}
}
impl Arbitrary for PublicKey {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let bytes: ByteArray<32> = Arbitrary::arbitrary(g);
let seed = Seed::new(bytes.into_inner());
let keypair = KeyPair::from_seed(seed);
PublicKey(keypair.pk)
}
}