228 lines · 7.0 KB · Rust
RawDownload
// Copyright 2023 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::fs;#[cfg(unix)]use std::fs::Permissions;use std::io::Write as _;#[cfg(unix)]use std::os::unix::prelude::PermissionsExt as _;use std::path::Path;use std::path::PathBuf; use jj_lib::signing::SigStatus;use jj_lib::signing::SigningBackend as _;use jj_lib::ssh_signing::SshBackend; static PRIVATE_KEY: &str = r#"-----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACBo/iejekjvuD/HTman0daImstssYYR52oB+dmr1KsOYQAAAIiuGFMFrhhTBQAAAAtzc2gtZWQyNTUxOQAAACBo/iejekjvuD/HTman0daImstssYYR52oB+dmr1KsOYQAAAECcUtn/J/jk/+D5+/+WbQRNN4eInj5L60pt6FioP0nQfGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5hAAAAAAECAwQF-----END OPENSSH PRIVATE KEY-----"#; static PUBLIC_KEY: &str =    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj+J6N6SO+4P8dOZqfR1oiay2yxhhHnagH52avUqw5h"; static FINGERPRINT: &str = "SHA256:CaeelDOMvTqGZPjAS9fdbnACrLg68N1Bb9ux5y6GjGw"; struct SshEnvironment {    _keys: tempfile::TempDir,    private_key_path: PathBuf,    allowed_signers: Option<tempfile::TempPath>,    revocation_list: Option<tempfile::TempPath>,} impl SshEnvironment {    fn new() -> Result<Self, std::process::Output> {        let keys_dir = tempfile::Builder::new()            .prefix("jj-test-signing-keys-")            .tempdir()            .unwrap();         let private_key_path = Path::new(keys_dir.path()).join("key");         fs::write(&private_key_path, PRIVATE_KEY).unwrap();         #[cfg(unix)]        std::fs::set_permissions(&private_key_path, Permissions::from_mode(0o600)).unwrap();         let mut env = Self {            _keys: keys_dir,            private_key_path,            allowed_signers: None,            revocation_list: None,        };         env.with_good_public_key();         Ok(env)    }     fn with_good_public_key(&mut self) {        let mut allowed_signers = tempfile::Builder::new()            .prefix("jj-test-allowed-signers-")            .tempfile()            .unwrap();         allowed_signers            .write_all("test@example.com ".as_bytes())            .unwrap();        allowed_signers.write_all(PUBLIC_KEY.as_bytes()).unwrap();        allowed_signers.flush().unwrap();         let allowed_signers_path = allowed_signers.into_temp_path();         self.allowed_signers = Some(allowed_signers_path);    }     fn with_bad_public_key(&mut self) {        let mut allowed_signers = tempfile::Builder::new()            .prefix("jj-test-allowed-signers-")            .tempfile()            .unwrap();         allowed_signers            .write_all("test@example.com ".as_bytes())            .unwrap();        allowed_signers            .write_all("INVALID PUBLIC KEY".as_bytes())            .unwrap();        allowed_signers.flush().unwrap();         let allowed_signers_path = allowed_signers.into_temp_path();         self.allowed_signers = Some(allowed_signers_path);    }     fn with_revocation_list(&mut self, revoked_key: &[u8]) {        let mut revocation_list = tempfile::Builder::new()            .prefix("jj-test-revocation-list-")            .tempfile()            .unwrap();         revocation_list.write_all(revoked_key).unwrap();        revocation_list.flush().unwrap();         let revocation_list_path = revocation_list.into_temp_path();         self.revocation_list = Some(revocation_list_path);    }} fn backend(env: &SshEnvironment) -> SshBackend {    SshBackend::new(        "ssh-keygen".into(),        env.allowed_signers            .as_ref()            .map(|allowed_signers| allowed_signers.as_os_str().into()),        env.revocation_list            .as_ref()            .map(|revocation_list| revocation_list.as_os_str().into()),    )} #[test]fn ssh_signing_roundtrip() {    let env = SshEnvironment::new().unwrap();    let backend = backend(&env);    let data = b"hello world";     let signature = backend        .sign(data, Some(env.private_key_path.to_str().unwrap()))        .unwrap();     let check = backend.verify(data, &signature).unwrap();    assert_eq!(check.status, SigStatus::Good);    assert_eq!(check.key.unwrap(), FINGERPRINT);    assert_eq!(check.display.unwrap(), "test@example.com");     let check = backend.verify(b"invalid-commit-data", &signature).unwrap();    assert_eq!(check.status, SigStatus::Bad);    assert_eq!(check.display.unwrap(), "test@example.com");} #[test]fn ssh_signing_bad_allowed_signers() {    let mut env = SshEnvironment::new().unwrap();    env.with_bad_public_key();     let backend = backend(&env);    let data = b"hello world";     let signature = backend        .sign(data, Some(env.private_key_path.to_str().unwrap()))        .unwrap();     let check = backend.verify(data, &signature).unwrap();    assert_eq!(check.status, SigStatus::Unknown);    assert_eq!(check.key.unwrap(), FINGERPRINT);    assert_eq!(check.display.unwrap(), "Signature OK. Unknown principal");} #[test]fn ssh_signing_missing_allowed_signers() {    let mut env = SshEnvironment::new().unwrap();    env.allowed_signers = None;     let backend = backend(&env);    let data = b"hello world";     let signature = backend        .sign(data, Some(env.private_key_path.to_str().unwrap()))        .unwrap();     let check = backend.verify(data, &signature).unwrap();    assert_eq!(check.status, SigStatus::Unknown);    assert_eq!(check.key.unwrap(), FINGERPRINT);    assert_eq!(check.display.unwrap(), "Signature OK. Unknown principal");} #[test]fn ssh_signing_revocation_revoked() {    let mut env = SshEnvironment::new().unwrap();    env.with_revocation_list(PUBLIC_KEY.as_bytes());     let backend = backend(&env);    let data = b"hello world";     let signature = backend        .sign(data, Some(env.private_key_path.to_str().unwrap()))        .unwrap();     let check = backend.verify(data, &signature).unwrap();    assert_eq!(check.status, SigStatus::Bad);    assert_eq!(check.display.unwrap(), "test@example.com");} #[test]fn ssh_signing_revocation_unrevoked() {    let mut env = SshEnvironment::new().unwrap();    const ALT_PUB_KEY: &str =        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrkiOk+QyRv87ahGdrxSin0DuNKezDDLE6lLkHxJpWU";    env.with_revocation_list(ALT_PUB_KEY.as_bytes());     let backend = backend(&env);    let data = b"hello world";     let signature = backend        .sign(data, Some(env.private_key_path.to_str().unwrap()))        .unwrap();     let check = backend.verify(data, &signature).unwrap();    assert_eq!(check.status, SigStatus::Good);    assert_eq!(check.key.unwrap(), FINGERPRINT);}