i2p / i2p.i2p

I2P is an anonymizing network, offering a simple layer that identity-sensitive applications can use to securely communicate. All data is wrapped with several layers of encryption, and the network is both distributed and dynamic, with no trusted parties.
https://geti2p.net
Other
1.93k stars 303 forks source link

I2P "B33" / Encrypted Base32 Address Encoding In Rust #36

Open bonedaddy opened 1 year ago

bonedaddy commented 1 year ago

Overview

Hello, I'm attempting to work with encrypted leasesets using rust, and am having difficulties calculating the encrypted/blinded destination address. Any attempt at using the derived b33 addresses results in a lookup error being returned:

Corrupt b32 address (or unsupported options)

Implementation

So far this is the function I have written modified from the java implementation

pub fn b33_address(
  public_key_data: &[u8], 
  public_key_type: u8,
  require_secret: bool, 
  client_auth: bool
) -> Option<String> {
    if public_key_type != 7 && public_key_type != 11 {
      return None;
    }
    let mut data_vec = Vec::with_capacity(public_key_data.len()+3);
    data_vec.extend_from_slice(&[0, 0, 0]);
    data_vec.extend_from_slice(public_key_data);
    let summer: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_CKSUM);
    let chk_sum = summer.checksum(&data_vec[3..]);
    if require_secret {
      data_vec[0] = 0x02;
    }
    if client_auth {
      data_vec[0] |= 0x04;
    }
    data_vec[1] = (public_key_type & 0xff) as u8;
    data_vec[2] = (11 & 0xff) as u8;
    data_vec[0] ^= chk_sum as u8;
    data_vec[1] ^= (chk_sum >> 8) as u8;
    data_vec[2] ^= (chk_sum >> 16) as u8;
    Some(format!("{}.b32.i2p", BASE32_I2P.encode(&data_vec)))
}

And here is how I'm testing:

    let pub_key = "3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbQ=".to_string();
    let pub_key = BASE64_I2P.decode(pub_key.as_bytes()).unwrap();
    let addr =b33_address(&pub_key[..],7, true, true).unwrap();
    println!("{}", addr);

Note that to generate the base64 string stored in the variable pub_key, I'm using the i2p-rs library, and running the following from

    let (pubkey, seckey) = {
        let mut sam_conn = SamConnection::connect(DEFAULT_API).unwrap();
        sam_conn
            .generate_destination(SignatureType::EdDsaSha512Ed25519)
            .unwrap()
    };
    let decoded = BASE64_I2P.decode(pubkey.as_bytes()).unwrap();
        // the output of this is stored in the `pub_key` variable
    println!("public key {}", BASE64_I2P.encode(&decoded[0..32]));

The result of the test is the following "b33" address m7nz7xpbohymusevu4lic3jmukc62ykf5r322vvfxgtwnrzirazadfnu.b32.i2p however when attempting to navigate to that address using my web browser, I receive the following error message

Corrupt b32 address (or unsupported options)

Key Material

I've included the following keys generated via the SAM api, using sig type 7 (EdDSA_SHA512_Ed25519)

Public Key:

3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN~I24i13ln4SUng99dgTQX33scumIuRGu38WuL80hc3BQAEAAcAAA==

Secret Key:

3eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN3hcfDKSJWnFoFtLKKF7WFF7HetVqW5p2bHKIgyAZW03eFx8MpIlacWgW0sooXtYUXsd61WpbmnZscoiDIBlbTd4XHwykiVpxaBbSyihe1hRex3rValuadmxyiIMgGVtN~I24i13ln4SUng99dgTQX33scumIuRGu38WuL80hc3BQAEAAcAAMTRtED335Fs3UCsTmT65U~tnkqMzcfGprDZ6UDKCFp~LPWL73lcud9JTpSk2JIRbskxFLKziPwTi-O2rC4nsjDDxLsHowR--R1bloIjn73S7T-Wcy2FCbZgsi~O~vhKEZohlnCI46GwgNJVwRHr0AvBfe4VrZFfF-ti73alo0Jv3m-C15vReuwS15JW1DN2mFI6FqB4bru0wIqi359JprVgTSrViIdNAmLpVtaoLuCAt-iT6Uhr2F6xncw~yz1UFmqpRQqszUTk09vluEKeWZomc9v7sPUvpCjQKoahD2pP7svlxuAXIDGGrTpNg3U~7vrp1eFXt1y6FzrRI6HzgnVfbU5EIqQTI~FqdQAr9uVV5590LzJfSShF32I83oMWDQ==
bonedaddy commented 1 year ago

Did a bit more reading and it looks like the b33_address function i wrote is wrong, as the address needs to be derived from the public key, which isn't returned directly by DEST GENERATE with the SAM API.

I've rewritten the function to what I believe is now the correct implementation, however I'm still unable to derive the address associated with the published encrypted leaseset :thinking:


pub fn b33_address(tun_conf: TunnelConfig, require_secret: bool, client_auth: bool) -> Option<String> {
    use crc::Crc;
    let public_key_type = match tun_conf.sam_options.signature_type {
        SignatureType::EdDsaSha512Ed25519 => 7,
        SignatureType::RedDsaSha512Ed25519 => 11,
        _ => return None,
    };
    // the private key returned from `DEST GENERATE`
    let decoded_private_key = BASE64_I2P.decode(tun_conf.secret_key.as_bytes()).ok()?;
    // the public key returned from `DEST GENERATE`
    let decoded_destination = BASE64_I2P.decode(tun_conf.public_key.as_bytes()).ok()?;
    let stripped_private_key = &decoded_private_key[decoded_destination.len()..];

    let private_key = &stripped_private_key[0..32];

    let public_key = if public_key_type == 7 {
        let sk = ed25519_dalek::SecretKey::from_bytes(private_key).ok()?;
        let pk: ed25519_dalek::PublicKey = (&sk).into();
        pk.as_bytes().to_vec()
    } else if public_key_type == 11 {
        let mut pk: [u8; 32] = [0_u8; 32];
        pk.copy_from_slice(private_key);
        let sk = x25519_dalek::StaticSecret::from(pk);
        let pk = x25519_dalek::PublicKey::from(&sk);
        pk.as_bytes().to_vec()
    } else {
        unreachable!()
    };
    let mut data_vec = Vec::with_capacity(public_key.len() + 3);
    data_vec.extend_from_slice(&[0, 0, 0]);
    data_vec.extend_from_slice(&public_key[..]);

    let summer: Crc<u32> = Crc::<u32>::new(&crc::CRC_32_CKSUM);
    let chk_sum = summer.checksum(&data_vec[3..]);

    if require_secret {
        data_vec[0] = 0x02;
    }
    if client_auth {
        data_vec[0] |= 0x04;
    }

    data_vec[1] = (public_key_type & 0xff) as u8;
    data_vec[2] = (11 & 0xff) as u8;

    data_vec[0] ^= chk_sum as u8;
    data_vec[1] ^= (chk_sum >> 8) as u8;
    data_vec[2] ^= (chk_sum >> 16) as u8;

    Some(format!("{}.b32.i2p", BASE32_I2P.encode(&data_vec)))
}
zzzi2p commented 1 year ago

base64 decode the full 'secret' string and save binary in privkeys.dat run: java -jar /path/to/i2p.jar privatekeyfile privkeys.dat compare what it gives you with your output if it's different, check format at top of PrivateKeyFile.java to see where you went wrong.