Also RSA identities on channel handshakes

This took a good bit of hacking, including a kludge to extract an
RSA subject key from an x509 cert, since we didn't have a good way
to do that.
This commit is contained in:
Nick Mathewson 2020-09-09 18:07:26 -04:00
parent 9a20a6244a
commit e02255ec00
8 changed files with 191 additions and 7 deletions

View File

@ -36,7 +36,7 @@ async fn connect<C: ChanTarget>(target: &C) -> Result<Channel<TlsStream<net::Tcp
let chan = OutboundClientHandshake::new(tlscon).connect().await?;
info!("version negotiated and cells read.");
let chan = chan.check(target, &peer_cert)?;
info!("Certs validated (except for RSA :/)");
info!("Certs validated");
let chan = chan.finish(&addr.ip()).await?;
info!("Channel complete.");

View File

@ -12,6 +12,7 @@ tor-llcrypto = { path="../tor-llcrypto" }
tor-bytes = { path="../tor-bytes" }
tor-checkable = { path="../tor-checkable" }
signature = "1.2.2"
digest = "*"
[dev-dependencies]
base64 = "0.12.3"

View File

@ -32,6 +32,8 @@
#![deny(missing_docs)]
pub mod rsa;
use caret::caret_int;
use signature::{Signer, Verifier};
use tor_bytes::{Error, Result};

87
tor-cert/src/rsa.rs Normal file
View File

@ -0,0 +1,87 @@
//! RSA->Ed25519 cross-certificates
//!
//! These are used in the Tor link handshake to prove that a given ed25519
//! key speaks for a given (deprecated) RSA identity.
use tor_bytes::Reader;
use tor_checkable::{timed::TimerangeBound, ExternallySigned};
use tor_llcrypto as ll;
use digest::Digest;
/// A RSA->Ed25519 cross-certificate
///
/// This kind of certificate is used in the channel handshake to prove
/// that the Ed25519 identity key speaks on behalf of the RSA identity key.
#[must_use]
pub struct RSACrosscert {
subject_key: ll::pk::ed25519::PublicKey,
exp_hours: u32,
digest: [u8; 32],
signature: Vec<u8>,
}
impl RSACrosscert {
/// Return the time at which this certificate becomes expired
pub fn get_expiry(&self) -> std::time::SystemTime {
let d = std::time::Duration::new((self.exp_hours as u64) * 3600, 0);
std::time::SystemTime::UNIX_EPOCH + d
}
/// Return true if the subject key in this certificate matches `other`
pub fn subject_key_matches(&self, other: &ll::pk::ed25519::PublicKey) -> bool {
&self.subject_key == other
}
/// Decode a slice of bytes into an RSA crosscert.
pub fn decode(bytes: &[u8]) -> tor_bytes::Result<UncheckedRSACrosscert> {
let mut r = Reader::from_slice(bytes);
let signed_portion = r.peek(36)?; // a bit ugly XXXX
let subject_key = r.extract()?;
let exp_hours = r.take_u32()?;
let siglen = r.take_u8()?;
let signature = r.take(siglen as usize)?.into();
let mut d = ll::d::Sha256::new();
d.update(&b"Tor TLS RSA/Ed25519 cross-certificate"[..]);
d.update(signed_portion);
let digest = d.finalize().into();
let cc = RSACrosscert {
subject_key,
exp_hours,
digest,
signature,
};
Ok(UncheckedRSACrosscert(cc))
}
}
/// An RSACrosscert whos signature has not been checked.
pub struct UncheckedRSACrosscert(RSACrosscert);
impl ExternallySigned<TimerangeBound<RSACrosscert>> for UncheckedRSACrosscert {
type Key = ll::pk::rsa::PublicKey;
type KeyHint = ();
type Error = tor_bytes::Error;
fn key_is_correct(&self, _k: &Self::Key) -> Result<(), Self::KeyHint> {
// there is no way to check except for trying to verify the signature
Ok(())
}
fn is_well_signed(&self, k: &Self::Key) -> Result<(), Self::Error> {
k.verify(&self.0.digest[..], &self.0.signature[..])
// XXXX poor choice of error type
.map_err(|_| {
tor_bytes::Error::BadMessage("Invalid signature on RSA->Ed identity crosscert")
})?;
Ok(())
}
fn dangerously_assume_wellsigned(self) -> TimerangeBound<RSACrosscert> {
let expiration = self.0.get_expiry();
TimerangeBound::new(self.0, ..expiration)
}
}

View File

@ -16,3 +16,4 @@
pub mod cipher;
pub mod d;
pub mod pk;
pub mod util;

63
tor-llcrypto/src/util.rs Normal file
View File

@ -0,0 +1,63 @@
//! Utilities for cryptographic purposes
//!
//! For now, this just has a workaround for some other libraries'
//! lack of full x509 support
use simple_asn1::{oid, ASN1Block, BigUint, OID};
/// Given an X.509 certificate, return its SubjectPublicKey if that key
/// is an RSA key.
///
/// WARNING: Does not validate the X.509 certificate at all!
///
/// XXXXX This is a massive kludge.
pub fn x509_extract_rsa_subject_kludge(der: &[u8]) -> Option<crate::pk::rsa::PublicKey> {
//use ASN1Block::*;
let blocks = simple_asn1::from_der(der).ok()?;
let block = Asn1(blocks.get(0)?);
// TBSCertificate
let tbs_cert: Asn1 = block.into_seq()?.get(0)?.into();
// SubjectPublicKeyInfo
let spki: Asn1 = tbs_cert.into_seq()?.get(6)?.into();
let spki_members = spki.into_seq()?;
// Is it an RSA key?
let algid: Asn1 = spki_members.get(0)?.into();
let oid: Asn1 = algid.into_seq()?.get(0)?.into();
oid.must_be_rsa_oid()?;
// try to get the RSA key.
let key: Asn1 = spki_members.get(1)?.into();
crate::pk::rsa::PublicKey::from_der(key.to_bitstr()?)
}
struct Asn1<'a>(&'a ASN1Block);
impl<'a> From<&'a ASN1Block> for Asn1<'a> {
fn from(b: &'a ASN1Block) -> Asn1<'a> {
Asn1(b)
}
}
impl<'a> Asn1<'a> {
fn into_seq(self) -> Option<&'a [ASN1Block]> {
match self.0 {
ASN1Block::Sequence(_, ref s) => Some(s),
_ => None,
}
}
fn must_be_rsa_oid(self) -> Option<()> {
let oid = match self.0 {
ASN1Block::ObjectIdentifier(_, ref oid) => Some(oid),
_ => None,
}?;
if oid == oid!(1, 2, 840, 113549, 1, 1, 1) {
Some(())
} else {
None
}
}
fn to_bitstr(&self) -> Option<&[u8]> {
match self.0 {
ASN1Block::BitString(_, _, ref v) => Some(&v[..]),
_ => None,
}
}
}

View File

@ -606,16 +606,22 @@ pub struct Certs {
certs: Vec<TorCert>,
}
impl Certs {
/// Return the body of the certificate tagged with 'tp', if any.
pub fn get_cert_body(&self, tp: tor_cert::CertType) -> Option<&[u8]> {
self.certs
.iter()
.find(|c| c.certtype == tp.into())
.map(|c| &c.cert[..])
}
/// Look for a certificate of type 'tp' in this cell; return it if
/// there is one.
pub fn parse_ed_cert(&self, tp: tor_cert::CertType) -> crate::Result<tor_cert::KeyUnknownCert> {
let cert = self
.certs
.iter()
.find(|c| c.certtype == tp.into())
let body = self
.get_cert_body(tp)
.ok_or_else(|| crate::Error::ChanProto(format!("Missing {} certificate", tp)))?;
let cert = tor_cert::Ed25519Cert::decode(&cert.cert)?;
let cert = tor_cert::Ed25519Cert::decode(body)?;
if cert.peek_cert_type() != tp {
return Err(crate::Error::ChanProto(format!(
"Found a {} certificate labeled as {}",

View File

@ -141,7 +141,6 @@ impl<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
let c = &self.certs_cell;
let id_sk = c.parse_ed_cert(CertType::IDENTITY_V_SIGNING)?;
let sk_tls = c.parse_ed_cert(CertType::SIGNING_V_TLS_CERT)?;
// XXXX need to verify RSA crosscert.
let id_sk = id_sk
.check_key(&None)?
@ -173,10 +172,35 @@ impl<T: AsyncRead + AsyncWrite + Unpin> UnverifiedChannel<T> {
));
}
let pkrsa = c
.get_cert_body(2.into()) // XXX use a constant.
.map(ll::util::x509_extract_rsa_subject_kludge)
.flatten()
.ok_or_else(|| Error::ChanProto("Couldn't find RSA identity key".into()))?;
let rsa_cert = c
.get_cert_body(7.into()) // XXXX use a constant
.ok_or_else(|| Error::ChanProto("No RSA->Ed crosscert".into()))?;
let rsa_cert = tor_cert::rsa::RSACrosscert::decode(rsa_cert)?
.check_signature(&pkrsa)
.map_err(|_| Error::ChanProto("Bad RSA->Ed crosscert signature".into()))?
.check_valid_now()
.map_err(|_| Error::ChanProto("RSA->Ed crosscert expired or invalid".into()))?;
if !rsa_cert.subject_key_matches(identity_key) {
return Err(Error::ChanProto(
"RSA->Ed crosscert certifies incorrect key".into(),
));
}
if identity_key != peer.get_ed_identity() {
return Err(Error::ChanProto("Peer ed25519 id not as expected".into()));
}
if &pkrsa.to_rsa_identity() != peer.get_rsa_identity() {
return Err(Error::ChanProto("Peer RSA id not as expected".into()));
}
Ok(VerifiedChannel {
link_protocol: self.link_protocol,
tls: self.tls,