@@ -650,6 +650,15 @@ dependencies = [
"byteorder",
]
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "dirs"
version = "1.0.5"
@@ -1109,6 +1118,16 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getrandom"
version = "0.1.16"
@@ -1380,6 +1399,9 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+dependencies = [
+ "spin",
+]
[[package]]
name = "lazycell"
@@ -1415,6 +1437,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "libm"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
+
[[package]]
name = "lock_api"
version = "0.4.2"
@@ -1569,6 +1597,35 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "num-bigint"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-integer",
+ "num-traits 0.2.14",
+]
+
+[[package]]
+name = "num-bigint-dig"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480"
+dependencies = [
+ "autocfg 0.1.7",
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits 0.2.14",
+ "rand 0.8.3",
+ "smallvec",
+ "zeroize",
+]
+
[[package]]
name = "num-integer"
version = "0.1.44"
@@ -1616,6 +1673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg 1.0.1",
+ "libm",
]
[[package]]
@@ -1733,6 +1791,17 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+[[package]]
+name = "pem"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
+dependencies = [
+ "base64",
+ "once_cell",
+ "regex",
+]
+
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -2224,6 +2293,26 @@ dependencies = [
"xmlparser",
]
+[[package]]
+name = "rsa"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ef841a26fc5d040ced0417c6c6a64ee851f42489df11cdf0218e545b6f8d28"
+dependencies = [
+ "byteorder",
+ "digest",
+ "lazy_static",
+ "num-bigint-dig",
+ "num-integer",
+ "num-iter",
+ "num-traits 0.2.14",
+ "pem",
+ "rand 0.8.3",
+ "simple_asn1",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "rust-argon2"
version = "0.8.3"
@@ -2484,6 +2573,18 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec"
+[[package]]
+name = "simple_asn1"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc31e6cf34ad4321d3a2b8f934949b429e314519f753a77962f16c664dca8e13"
+dependencies = [
+ "chrono",
+ "num-bigint",
+ "num-traits 0.2.14",
+ "thiserror",
+]
+
[[package]]
name = "simplecss"
version = "0.2.0"
@@ -2545,6 +2646,12 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -2563,6 +2670,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+[[package]]
+name = "subtle"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
+
[[package]]
name = "svg_fmt"
version = "0.4.1"
@@ -2600,6 +2713,18 @@ dependencies = [
"unicode-xid",
]
+[[package]]
+name = "synstructure"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
[[package]]
name = "take_mut"
version = "0.2.2"
@@ -2784,6 +2909,12 @@ version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e00391c1f3d171490a3f8bd79999b0002ae38d3da0d6a3a306c754b053d71b"
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
[[package]]
name = "unicode-bidi"
version = "0.3.4"
@@ -3059,6 +3190,7 @@ dependencies = [
"parking_lot",
"postage",
"rand 0.8.3",
+ "rsa",
"rust-embed",
"seahash",
"serde 1.0.125",
@@ -3073,4 +3205,36 @@ dependencies = [
"tree-sitter-rust",
"unindent",
"url",
+ "zed-rpc",
+]
+
+[[package]]
+name = "zed-rpc"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "base64",
+ "rand 0.8.3",
+ "rsa",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
]
@@ -0,0 +1,122 @@
+use std::convert::{TryFrom, TryInto};
+
+use anyhow::{Context, Result};
+use rand::{rngs::OsRng, Rng as _};
+use rsa::{PublicKey as _, PublicKeyEncoding, RSAPrivateKey, RSAPublicKey};
+
+pub struct PublicKey(RSAPublicKey);
+
+pub struct PrivateKey(RSAPrivateKey);
+
+/// Generate a public and private key for asymmetric encryption.
+pub fn keypair() -> Result<(PublicKey, PrivateKey)> {
+ let mut rng = OsRng;
+ let bits = 1024;
+ let private_key = RSAPrivateKey::new(&mut rng, bits)?;
+ let public_key = RSAPublicKey::from(&private_key);
+ Ok((PublicKey(public_key), PrivateKey(private_key)))
+}
+
+/// Generate a random 64-character base64 string.
+pub fn random_token() -> String {
+ let mut rng = OsRng;
+ let mut token_bytes = [0; 48];
+ for byte in token_bytes.iter_mut() {
+ *byte = rng.gen();
+ }
+ base64::encode(&token_bytes)
+}
+
+impl PublicKey {
+ /// Convert a string to a base64-encoded string that can only be decoded with the corresponding
+ /// private key.
+ pub fn encrypt_string(&self, string: &str) -> Result<String> {
+ let mut rng = OsRng;
+ let bytes = string.as_bytes();
+ let encrypted_bytes = self
+ .0
+ .encrypt(&mut rng, PADDING_SCHEME, bytes)
+ .context("failed to encrypt string with public key")?;
+ let encrypted_string = base64::encode(&encrypted_bytes);
+ Ok(encrypted_string)
+ }
+}
+
+impl PrivateKey {
+ /// Decrypt a base64-encoded string that was encrypted by the correspoding public key.
+ pub fn decrypt_string(&self, encrypted_string: &str) -> Result<String> {
+ let encrypted_bytes =
+ base64::decode(encrypted_string).context("failed to base64-decode encrypted string")?;
+ let bytes = self
+ .0
+ .decrypt(PADDING_SCHEME, &encrypted_bytes)
+ .context("failed to decrypt string with private key")?;
+ let string = String::from_utf8(bytes).context("decrypted content was not valid utf8")?;
+ Ok(string)
+ }
+}
+
+impl TryInto<String> for PublicKey {
+ type Error = anyhow::Error;
+ fn try_into(self) -> Result<String> {
+ let bytes = self
+ .0
+ .to_pkcs1()
+ .context("failed to serialize public key")?;
+ let string = base64::encode(&bytes);
+ Ok(string)
+ }
+}
+
+impl TryFrom<String> for PublicKey {
+ type Error = anyhow::Error;
+ fn try_from(value: String) -> Result<Self> {
+ let bytes = base64::decode(&value).context("failed to base64-decode public key string")?;
+ let key = Self(RSAPublicKey::from_pkcs1(&bytes).context("failed to parse public key")?);
+ Ok(key)
+ }
+}
+
+const PADDING_SCHEME: rsa::PaddingScheme = rsa::PaddingScheme::PKCS1v15Encrypt;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_generate_encrypt_and_decrypt_token() {
+ // CLIENT:
+ // * generate a keypair for asymmetric encryption
+ // * serialize the public key to send it to the server.
+ let (public, private) = keypair().unwrap();
+ let public_string: String = public.try_into().unwrap();
+
+ // SERVER:
+ // * parse the public key
+ // * generate a random token.
+ // * encrypt the token using the public key.
+ let public: PublicKey = public_string.try_into().unwrap();
+ let token = random_token();
+ let encrypted_token = public.encrypt_string(&token).unwrap();
+ assert_eq!(token.len(), 64);
+ assert_ne!(encrypted_token, token);
+ assert_printable(&token);
+ assert_printable(&encrypted_token);
+
+ // CLIENT:
+ // * decrypt the token using the private key.
+ let decrypted_token = private.decrypt_string(&encrypted_token).unwrap();
+ assert_eq!(decrypted_token, token);
+ }
+
+ fn assert_printable(token: &str) {
+ for c in token.chars() {
+ assert!(
+ c.is_ascii_graphic(),
+ "token {:?} has non-printable char {}",
+ token,
+ c
+ );
+ }
+ }
+}
@@ -1,3 +1,5 @@
+use std::convert::TryInto;
+
use anyhow::{anyhow, Context};
use gpui::MutableAppContext;
use smol::io::{AsyncBufReadExt, AsyncWriteExt};
@@ -33,15 +35,18 @@ fn authenticate(_: &(), cx: &mut MutableAppContext) {
let zed_url = std::env::var("ZED_SERVER_URL").unwrap_or("https://zed.dev".to_string());
let platform = cx.platform().clone();
- dbg!(&zed_url);
-
let task = cx.background_executor().spawn(async move {
let listener = smol::net::TcpListener::bind("127.0.0.1:0").await?;
let port = listener.local_addr()?.port();
+ let (public_key, private_key) =
+ zed_rpc::auth::keypair().expect("failed to generate keypair for auth");
+
+ let public_key_string: String = public_key.try_into().unwrap();
+
platform.open_url(&format!(
- "{}/sign_in?native_app_port={}&native_app_public_key=unused-for-now",
- zed_url, port,
+ "{}/sign_in?native_app_port={}&native_app_public_key={}",
+ zed_url, port, public_key_string
));
let (mut stream, _) = listener.accept().await?;
@@ -54,13 +59,13 @@ fn authenticate(_: &(), cx: &mut MutableAppContext) {
if let Some(path) = parts.next() {
let url = Url::parse(&format!("http://example.com{}", path))
.context("failed to parse login notification url")?;
+ let mut user_id = None;
let mut access_token = None;
- let mut public_key = None;
for (key, value) in url.query_pairs() {
if key == "access_token" {
access_token = Some(value);
- } else if key == "public_key" {
- public_key = Some(value);
+ } else if key == "user_id" {
+ user_id = Some(value);
}
}
stream
@@ -69,10 +74,13 @@ fn authenticate(_: &(), cx: &mut MutableAppContext) {
.context("failed to write login response")?;
stream.flush().await.context("failed to flush tcp stream")?;
- eprintln!(
- "logged in. access_token: {:?}, public_key: {:?}",
- access_token, public_key
- );
+ if let Some((user_id, access_token)) = user_id.zip(access_token) {
+ let access_token = private_key.decrypt_string(&access_token);
+ eprintln!(
+ "logged in. user_id: {}, access_token: {:?}",
+ user_id, access_token
+ );
+ }
platform.activate(true);
return Ok(());