1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
extern crate yasna;
extern crate base64;
extern crate num;
extern crate byteorder;

const RSA_ENCRYPTION: [u64; 7] = [1, 2, 840, 113549, 1, 1, 1];
const SSH_RSA_TEXT: &str = "ssh-rsa";

use std::io::{Read, BufRead};
use std::io::{self, BufReader};
use num::bigint::{BigInt, Sign};
use base64::{decode, encode, DecodeError};
use yasna::{parse_der, ASN1Error};
use yasna::models::ObjectIdentifier;
use byteorder::BigEndian;
use byteorder::WriteBytesExt;

#[derive(Debug)]
pub enum Error {
    IoError(io::Error),
    DecodeError(DecodeError),
    ASN1Error(ASN1Error),
    InvalidSshaRsa,
}

#[derive(Debug)]
struct PublicDer {
    object_identifier: ObjectIdentifier,
    exponent: BigInt,
    modulus: BigInt,
}

// TODO: check header name
pub fn pem_to_der<R: Read>(pem: &mut R) -> Result<Vec<u8>, Error> {
    let buff = BufReader::new(pem);
    let mut pem = String::new();

    for line in buff.lines() {
        let line = line.map_err(Error::IoError)?;
        if !line.starts_with('-') {
            pem.extend(line.chars());
        }
    }

    Ok(decode(&pem).map_err(Error::DecodeError)?)
}

pub fn der_to_openssh(der: &[u8]) -> Result<String, Error> {
    let public_der = parse_der(der, |reader| {
        reader.read_sequence(|reader| {
            let oid = reader.next().read_sequence(|reader| {
                let oid = reader.next().read_oid()?;
                let _ = reader.next().read_null()?;
                Ok(oid)
            })?;

            let bs = reader.next().read_bitvec()?.to_bytes();
            let (n, e) = parse_der(&bs, |reader| {
                reader.read_sequence(|reader| {
                    let n = reader.next().read_bigint()?;
                    let e = reader.next().read_bigint()?;
                    Ok((n, e))
                })
            })?;

            Ok(PublicDer {
                object_identifier: oid,
                exponent: e,
                modulus: n,
            })
        })
    }).map_err(Error::ASN1Error)?;

    let iod = public_der.object_identifier.components().as_slice();
    if iod != RSA_ENCRYPTION {
        return Err(Error::InvalidSshaRsa);
    }

    // TODO: compute capacity
    let mut openssh_key = Vec::new();

    // write the size of the 'ssh-rsa' text
    let ssh_rsa_text_len = SSH_RSA_TEXT.len() as u32;
    openssh_key.write_u32::<BigEndian>(ssh_rsa_text_len).map_err(Error::IoError)?;
    // write the 'ssh-rsa' text itself
    openssh_key.extend_from_slice(SSH_RSA_TEXT.as_bytes());

    // write the size of the exponent
    let exp_bits_size = (public_der.exponent.bits() / 8 + 1) as u32;
    openssh_key.write_u32::<BigEndian>(exp_bits_size).map_err(Error::IoError)?;
    let (sign, mut bytes) = public_der.exponent.to_bytes_be();
    // add a byte to toggle the sign bit
    if bytes.len() < exp_bits_size as usize {
        bytes.insert(0, 0);
    }
    bytes[0] |= if sign == Sign::Minus { 0b1000_0000 } else { 0 };
    // write the exponent itself (with sign bit)
    openssh_key.extend_from_slice(&bytes);

    // write the size of the modulus
    let mod_bits_size = (public_der.modulus.bits() / 8 + 1) as u32;
    openssh_key.write_u32::<BigEndian>(mod_bits_size).map_err(Error::IoError)?;

    let (sign, mut bytes) = public_der.modulus.to_bytes_be();
    // add a byte to toggle the sign bit
    if bytes.len() < mod_bits_size as usize {
        bytes.insert(0, 0);
    }
    bytes[0] |= if sign == Sign::Minus { 0b1000_0000 } else { 0 };
    // write the modulus itself (with sign bit)
    openssh_key.extend_from_slice(&bytes);

    let mut openssh_key = encode(&openssh_key);
    openssh_key.insert_str(0, "ssh-rsa ");
    Ok(openssh_key)
}