Commit 8c032379 authored by Jordan Petridis's avatar Jordan Petridis 🌱 Committed by Sebastian Dröge

Add libsodium-based encrypter/decrypter elements

With some changes by Sebastian Dröge <sebastian@centricular.com>
parent 075cb97b
Pipeline #35915 passed with stages
in 10 minutes and 31 seconds
......@@ -12,8 +12,11 @@ stages:
stage: "test"
variables:
G_DEBUG: "fatal_warnings"
SODIUM_USE_PKG_CONFIG: "true"
DEPENDENCIES: |
curl
file
libsodium-dev
libssl-dev
liborc-0.4-dev
libglib2.0-dev
......@@ -54,10 +57,10 @@ stages:
- cargo test --all-features --all --color=always
- cargo build --all-features --examples --all --color=always
test 1.31:
# 1.31 img
test 1.32:
# 1.32 img
# https://hub.docker.com/_/rust/
image: "rust:1.31-slim"
image: "rust:1.32-slim"
<<: *cargo_test
test stable:
......
......@@ -9,6 +9,7 @@ members = [
"gst-plugin-threadshare",
"gst-plugin-tutorial",
"gst-plugin-closedcaption",
"gst-plugin-sodium",
]
[profile.release]
......
[package]
name = "gst-plugin-sodium"
version = "0.1.0"
authors = ["Jordan Petridis <jordan@centricular.com>"]
edition = "2018"
[dependencies]
glib = { git = "https://github.com/gtk-rs/glib" }
gst = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing", "v1_14"], package="gstreamer" }
gst-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing", "v1_14"], package = "gstreamer-base" }
sodiumoxide = "0.2.1"
lazy_static = "1.3.0"
hex = "0.3.2"
smallvec = "0.6"
# example
clap = { version = "2.33", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }
[dev-dependencies]
pretty_assertions = "0.6"
rand = "0.6"
[dev-dependencies.gst-check]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
package="gstreamer-check"
[dev-dependencies.gst-app]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
package="gstreamer-app"
[lib]
name = "gstrssodium"
crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[[example]]
name = "generate-keys"
path = "examples/generate_keys.rs"
required-features = ["serde", "serde_json", "clap"]
[[example]]
name = "encrypt-example"
path = "examples/encrypt_example.rs"
required-features = ["serde", "serde_json", "clap"]
[[example]]
name = "decrypt-example"
path = "examples/decrypt_example.rs"
required-features = ["serde", "serde_json", "clap"]
// decrypt_example.rs
//
// Copyright 2019 Jordan Petridis <jordan@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT
use glib::prelude::*;
use gst::prelude::*;
use sodiumoxide::crypto::box_;
use std::error::Error;
use std::fs::File;
use std::path::PathBuf;
use clap::{App, Arg};
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct Keys {
public: box_::PublicKey,
private: box_::SecretKey,
}
impl Keys {
fn from_file(file: &PathBuf) -> Result<Self, Box<dyn Error>> {
let f = File::open(&file)?;
serde_json::from_reader(f).map_err(From::from)
}
}
fn main() -> Result<(), Box<dyn Error>> {
let matches = App::new("Decrypt a gstsodium10 file.")
.version("1.0")
.author("Jordan Petridis <jordan@centricular.com>")
.arg(
Arg::with_name("input")
.short("i")
.long("input")
.value_name("FILE")
.help("File to encrypt")
.required(true)
.takes_value(true),
)
.arg(
Arg::with_name("output")
.short("o")
.long("output")
.value_name("FILE")
.help("File to decrypt")
.required(true)
.takes_value(true),
)
.get_matches();
gst::init()?;
let input_loc = matches.value_of("input").unwrap();
let out_loc = matches.value_of("output").unwrap();
let receiver_keys = {
let mut r = PathBuf::new();
r.push(env!("CARGO_MANIFEST_DIR"));
r.push("examples");
r.push("receiver_sample");
r.set_extension("json");
r
};
let sender_keys = {
let mut s = PathBuf::new();
s.push(env!("CARGO_MANIFEST_DIR"));
s.push("examples");
s.push("sender_sample");
s.set_extension("json");
s
};
let receiver = &Keys::from_file(&receiver_keys)?;
let sender = &Keys::from_file(&sender_keys)?;
let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
let decrypter = gst::ElementFactory::make("rssodiumdecrypter", None).unwrap();
let typefind = gst::ElementFactory::make("typefind", None).unwrap();
let filesink = gst::ElementFactory::make("filesink", None).unwrap();
filesrc
.set_property("location", &input_loc)
.expect("Failed to set location property");
filesink
.set_property("location", &out_loc)
.expect("Failed to set location property");
decrypter.set_property("receiver-key", &glib::Bytes::from_owned(receiver.private.0))?;
decrypter.set_property("sender-key", &glib::Bytes::from_owned(sender.public))?;
let pipeline = gst::Pipeline::new(Some("test-pipeline"));
pipeline
.add_many(&[&filesrc, &decrypter, &typefind, &filesink])
.expect("failed to add elements to the pipeline");
gst::Element::link_many(&[&filesrc, &decrypter, &typefind, &filesink])
.expect("failed to link the elements");
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
let bus = pipeline.get_bus().unwrap();
for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Error(err) => {
eprintln!(
"Error received from element {:?}: {}",
err.get_src().map(|s| s.get_path_string()),
err.get_error()
);
eprintln!("Debugging information: {:?}", err.get_debug());
break;
}
MessageView::Eos(..) => break,
_ => (),
}
}
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Playing` state");
Ok(())
}
// encrypt_example.rs
//
// Copyright 2019 Jordan Petridis <jordan@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT
use glib::prelude::*;
use gst::prelude::*;
use sodiumoxide::crypto::box_;
use std::error::Error;
use std::fs::File;
use std::path::PathBuf;
use clap::{App, Arg};
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Serialize, Deserialize)]
struct Keys {
public: box_::PublicKey,
private: box_::SecretKey,
}
impl Keys {
fn from_file(file: &PathBuf) -> Result<Self, Box<dyn Error>> {
let f = File::open(&file)?;
serde_json::from_reader(f).map_err(From::from)
}
}
fn main() -> Result<(), Box<dyn Error>> {
let matches = App::new("Encrypt a file with in the gstsodium10 format")
.version("1.0")
.author("Jordan Petridis <jordan@centricular.com>")
.arg(
Arg::with_name("input")
.short("i")
.long("input")
.value_name("FILE")
.help("File to encrypt")
.required(true)
.takes_value(true),
)
.arg(
Arg::with_name("output")
.short("o")
.long("output")
.value_name("FILE")
.help("File to decrypt")
.required(true)
.takes_value(true),
)
.get_matches();
gst::init()?;
let input_loc = matches.value_of("input").unwrap();
let out_loc = matches.value_of("output").unwrap();
let receiver_keys = {
let mut r = PathBuf::new();
r.push(env!("CARGO_MANIFEST_DIR"));
r.push("examples");
r.push("receiver_sample");
r.set_extension("json");
r
};
let sender_keys = {
let mut s = PathBuf::new();
s.push(env!("CARGO_MANIFEST_DIR"));
s.push("examples");
s.push("sender_sample");
s.set_extension("json");
s
};
let receiver = &Keys::from_file(&receiver_keys)?;
let sender = &Keys::from_file(&sender_keys)?;
let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
let encrypter = gst::ElementFactory::make("rssodiumencrypter", None).unwrap();
let filesink = gst::ElementFactory::make("filesink", None).unwrap();
filesrc
.set_property("location", &input_loc)
.expect("Failed to set location property");
filesink
.set_property("location", &out_loc)
.expect("Failed to set location property");
encrypter.set_property("receiver-key", &glib::Bytes::from_owned(receiver.public))?;
encrypter.set_property("sender-key", &glib::Bytes::from_owned(sender.private.0))?;
let pipeline = gst::Pipeline::new(Some("test-pipeline"));
pipeline
.add_many(&[&filesrc, &encrypter, &filesink])
.expect("failed to add elements to the pipeline");
gst::Element::link_many(&[&filesrc, &encrypter, &filesink])
.expect("failed to link the elements");
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline.get_bus().unwrap();
for msg in bus.iter_timed(gst::CLOCK_TIME_NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Error(err) => {
eprintln!(
"Error received from element {:?}: {}",
err.get_src().map(|s| s.get_path_string()),
err.get_error()
);
eprintln!("Debugging information: {:?}", err.get_debug());
break;
}
MessageView::Eos(..) => break,
_ => (),
}
}
pipeline.set_state(gst::State::Null)?;
Ok(())
}
// generate_keys.rs
//
// Copyright 2019 Jordan Petridis <jordan@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT
use clap::{App, Arg};
use serde::{Deserialize, Serialize};
use serde_json;
use sodiumoxide::crypto::box_;
use std::fs::File;
#[derive(Debug, Serialize, Deserialize)]
struct Keys {
public: box_::PublicKey,
private: box_::SecretKey,
}
impl Keys {
fn new() -> Self {
let (public, private) = box_::gen_keypair();
Keys { public, private }
}
fn write_to_file(&self, path: &str, json: bool) {
if json {
let path = if !path.ends_with(".json") {
format!("{}.json", path)
} else {
path.into()
};
let file = File::create(&path).expect(&format!("Failed to create file at {}", path));
serde_json::to_writer(file, &self)
.expect(&format!("Failed to write to file at {}", path));
} else {
use std::io::Write;
use std::path::PathBuf;
let mut private = PathBuf::from(path);
private.set_extension("prv");
let mut file = File::create(&private)
.expect(&format!("Failed to create file at {}", private.display()));
file.write_all(&self.private.0)
.expect(&format!("Failed to write to file at {}", private.display()));
let mut public = PathBuf::from(path);
public.set_extension("pub");
let mut file = File::create(&public)
.expect(&format!("Failed to create file at {}", public.display()));
file.write_all(self.public.as_ref())
.expect(&format!("Failed to write to file at {}", public.display()));
}
}
}
fn main() {
let matches = App::new("Generate the keys to be used with the sodium element")
.version("1.0")
.author("Jordan Petridis <jordan@centricular.com>")
.about("Generate a pair of Sodium's crypto_box_curve25519xsalsa20poly1305 keys.")
.arg(
Arg::with_name("path")
.long("path")
.short("p")
.value_name("FILE")
.help("Path to write the Keys")
.required(true)
.takes_value(true),
)
.arg(
Arg::with_name("json")
.long("json")
.short("j")
.help("Write a JSON file instead of a key.prv/key.pub pair"),
)
.get_matches();
let keys = Keys::new();
let path = matches.value_of("path").unwrap();
keys.write_to_file(path, matches.is_present("json"));
}
{"public":[28,95,33,124,28,103,80,78,7,28,234,40,226,179,253,166,169,64,78,5,57,92,151,179,221,89,68,70,44,225,219,19],"private":[54,221,217,54,94,235,167,2,187,249,71,31,59,27,19,166,78,236,102,48,29,142,41,189,22,146,218,69,147,165,240,235]}
{"public":[66,248,199,74,216,55,228,116,52,17,147,56,65,130,134,148,157,153,235,171,179,147,120,71,100,243,133,120,160,14,111,65],"private":[154,227,90,239,206,184,202,234,176,161,14,91,218,98,142,13,145,223,210,222,224,240,98,51,142,165,255,1,159,100,242,162]}
This diff is collapsed.
This diff is collapsed.
// lib.rs
//
// Copyright 2019 Jordan Petridis <jordan@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT
#![crate_type = "cdylib"]
#[macro_use]
extern crate glib;
#[macro_use]
extern crate gst;
#[macro_use]
extern crate lazy_static;
const TYPEFIND_HEADER: &[u8; 12] = b"gst-sodium10";
// `core::slice::<impl [T]>::len` is not yet stable as a const fn
// const TYPEFIND_HEADER_SIZE: usize = TYPEFIND_HEADER.len();
const TYPEFIND_HEADER_SIZE: usize = 12;
/// Encryted steams use an offset at the start to store the block_size
/// and the nonce that was used.
const HEADERS_SIZE: usize =
TYPEFIND_HEADER_SIZE + sodiumoxide::crypto::box_::NONCEBYTES + std::mem::size_of::<u32>();
mod decrypter;
mod encrypter;
fn typefind_register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
use glib::translate::ToGlib;
use gst::{Caps, TypeFind, TypeFindProbability};
TypeFind::register(
plugin,
"sodium_encrypted_typefind",
gst::Rank::Primary.to_glib() as u32,
None,
&Caps::new_simple("application/x-sodium-encrypted", &[]),
|typefind| {
if let Some(data) = typefind.peek(0, TYPEFIND_HEADER_SIZE as u32) {
if data == TYPEFIND_HEADER {
typefind.suggest(
TypeFindProbability::Maximum,
&Caps::new_simple("application/x-sodium-encrypted", &[]),
);
}
}
},
)
}
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
encrypter::register(plugin)?;
decrypter::register(plugin)?;
typefind_register(plugin)?;
Ok(())
}
gst_plugin_define!(
"rssodium",
"libsodium-based file encryption and decryption",
plugin_init,
"1.0",
"MIT/X11",
"rssodium",
"rssodium",
"https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs",
"2019-03-18"
);
This diff is collapsed.
// encrypter.rs
//
// Copyright 2019 Jordan Petridis <jordan@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// SPDX-License-Identifier: MIT
#[macro_use]
extern crate lazy_static;
extern crate gstrssodium;
use glib::prelude::*;
use gst::prelude::*;
lazy_static! {
static ref RECEIVER_PUBLIC: glib::Bytes = {
let public = [
28, 95, 33, 124, 28, 103, 80, 78, 7, 28, 234, 40, 226, 179, 253, 166, 169, 64, 78, 5,
57, 92, 151, 179, 221, 89, 68, 70, 44, 225, 219, 19,
];
glib::Bytes::from_owned(public)
};
static ref SENDER_PRIVATE: glib::Bytes = {
let secret = [
154, 227, 90, 239, 206, 184, 202, 234, 176, 161, 14, 91, 218, 98, 142, 13, 145, 223,
210, 222, 224, 240, 98, 51, 142, 165, 255, 1, 159, 100, 242, 162,
];
glib::Bytes::from_owned(secret)
};
static ref NONCE: glib::Bytes = {
let nonce = [
144, 187, 179, 230, 15, 4, 241, 15, 37, 133, 22, 30, 50, 106, 70, 159, 243, 218, 173,
22, 18, 36, 4, 45,
];
glib::Bytes::from_owned(nonce)
};
}
fn init() {
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
INIT.call_once(|| {
gst::init().unwrap();
gstrssodium::plugin_register_static().unwrap();
// set the nonce
std::env::set_var("GST_SODIUM_ENCRYPT_NONCE", hex::encode(&*NONCE));
});
}
#[test]
fn encrypt_file() {
init();
let input = include_bytes!("sample.mp3");
let expected_output = include_bytes!("encrypted_sample.enc");
let mut adapter = gst_base::UniqueAdapter::new();
let enc = gst::ElementFactory::make("rssodiumencrypter", None).unwrap();
enc.set_property("sender-key", &*SENDER_PRIVATE)
.expect("failed to set property");
enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
.expect("failed to set property");
enc.set_property("block-size", &1024u32)
.expect("failed to set property");
let mut h = gst_check::Harness::new_with_element(&enc, None, None);
h.add_element_src_pad(&enc.get_static_pad("src").expect("failed to get src pad"));
h.add_element_sink_pad(&enc.get_static_pad("sink").expect("failed to get src pad"));
h.set_src_caps_str("application/x-sodium-encrypted");
let buf = gst::Buffer::from_mut_slice(Vec::from(&input[..]));
assert_eq!(h.push(buf), Ok(gst::FlowSuccess::Ok));
h.push_event(gst::Event::new_eos().build());
println!("Pulling buffer...");
while let Some(buf) = h.pull() {
adapter.push(buf);
if adapter.available() >= expected_output.len() {
break;
}
}
let buf = adapter
.take_buffer(adapter.available())
.expect("failed to take buffer");
let map = buf.map_readable().expect("Couldn't map buffer readable");
assert_eq!(map.as_ref(), expected_output.as_ref());
}
#[test]
fn test_state_changes() {
init();
// NullToReady without keys provided
{
let enc = gst::ElementFactory::make("rssodiumencrypter", None).unwrap();
assert!(enc.change_state(gst::StateChange::NullToReady).is_err());
// Set only receiver key
let enc = gst::ElementFactory::make("rssodiumencrypter", None).unwrap();
enc.set_property("receiver-key", &*RECEIVER_PUBLIC)
.expect("failed to set property");
assert!(enc.change_state(gst::StateChange::NullToReady).is_err());