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:
parent
9a20a6244a
commit
e02255ec00
|
@ -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.");
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
pub mod rsa;
|
||||
|
||||
use caret::caret_int;
|
||||
use signature::{Signer, Verifier};
|
||||
use tor_bytes::{Error, Result};
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -16,3 +16,4 @@
|
|||
pub mod cipher;
|
||||
pub mod d;
|
||||
pub mod pk;
|
||||
pub mod util;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {}",
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue