netdoc: initial implementation of authority certs.

This commit is contained in:
Nick Mathewson 2020-06-09 17:23:45 -04:00
parent 2d8021420f
commit 3b4c828cb7
2 changed files with 242 additions and 0 deletions

197
tor-netdoc/src/authcert.rs Normal file
View File

@ -0,0 +1,197 @@
use crate::argtype::{ISO8601TimeSp, RSAPublic};
use crate::err::Pos;
use crate::keyword::Keyword;
use crate::parse::{Section, SectionRules};
use crate::tokenize::{ItemResult, NetDocReader};
use crate::{Error, Result};
use tor_llcrypto::d;
use tor_llcrypto::pk::rsa;
use lazy_static::lazy_static;
use std::{net, time};
use digest::Digest;
decl_keyword! {
AuthCertKW {
"dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
"dir-address" => DIR_ADDRESS,
"fingerprint" => FINGERPRINT,
"dir-identity-key" => DIR_IDENTITY_KEY,
"dir-key-published" => DIR_KEY_PUBLISHED,
"dir-key-expires" => DIR_KEY_EXPIRES,
"dir-signing-key" => DIR_SIGNING_KEY,
"dir-key-crosscert" => DIR_KEY_CROSSCERT,
"dir-key-certification" => DIR_KEY_CERTIFICATION,
}
}
lazy_static! {
static ref AUTHCERT_RULES: SectionRules<AuthCertKW> = {
use AuthCertKW::*;
let mut rules = SectionRules::new();
rules.add(DIR_KEY_CERTIFICATE_VERSION.rule().required().args(1..));
rules.add(DIR_ADDRESS.rule().args(1..));
rules.add(FINGERPRINT.rule().required().args(1..));
rules.add(DIR_IDENTITY_KEY.rule().required().no_args().obj_required());
rules.add(DIR_SIGNING_KEY.rule().required().no_args().obj_required());
rules.add(DIR_KEY_PUBLISHED.rule().required());
rules.add(DIR_KEY_EXPIRES.rule().required());
rules.add(DIR_KEY_CROSSCERT.rule().required().no_args().obj_required());
rules.add(
DIR_KEY_CERTIFICATION
.rule()
.required()
.no_args()
.obj_required(),
);
rules
};
}
pub struct AuthCert {
address: Option<net::SocketAddrV4>,
identity_key: rsa::PublicKey,
signing_key: rsa::PublicKey,
published: time::SystemTime,
expires: time::SystemTime,
}
impl AuthCert {
pub fn parse(s: &str) -> Result<AuthCert> {
let mut reader = NetDocReader::new(s);
let result = AuthCert::take_from_reader(&mut reader).map_err(|e| e.within(s));
reader.should_be_exhausted()?;
result
}
fn take_from_reader(reader: &mut NetDocReader<'_, AuthCertKW>) -> Result<AuthCert> {
use AuthCertKW::*;
let mut start_found = false;
let mut iter = reader.pause_at(|item| {
let is_start = item.is_ok_with_kwd(DIR_KEY_CERTIFICATE_VERSION);
let pause = is_start && start_found;
if is_start {
start_found = true;
}
pause
});
let body = AUTHCERT_RULES.parse(&mut iter)?;
// Make sure first and last element are correct types. We can
// safely call unwrap() on first and last, since there are required
// tokens in the rules, so we know that at least one token will have
// been parsed.
if body.first_item().unwrap().get_kwd() != DIR_KEY_CERTIFICATE_VERSION {
// TODO: this is not the best possible error.
return Err(Error::MissingToken("onion-key"));
}
if body.last_item().unwrap().get_kwd() != DIR_KEY_CERTIFICATION {
// TODO: this is not the best possible error.
return Err(Error::MissingToken("dir-key-certification"));
}
let version = body
.get_required(DIR_KEY_CERTIFICATE_VERSION)?
.get_arg(0)
.unwrap();
if version != "3" {
// TODO Better error needed
return Err(Error::Internal(Pos::None));
}
let signing_key: rsa::PublicKey = body
.get_required(DIR_SIGNING_KEY)?
.parse_obj::<RSAPublic>("RSA PUBLIC KEY")?
.check_len(1024..)?
.check_exponent(65537)?
.into();
let identity_key: rsa::PublicKey = body
.get_required(DIR_IDENTITY_KEY)?
.parse_obj::<RSAPublic>("RSA PUBLIC KEY")?
.check_len(1024..)?
.check_exponent(65537)?
.into();
let published = body
.get_required(DIR_KEY_PUBLISHED)?
.args_as_str()
.parse::<ISO8601TimeSp>()?
.into();
let expires = body
.get_required(DIR_KEY_EXPIRES)?
.args_as_str()
.parse::<ISO8601TimeSp>()?
.into();
// TODO: Check fingerprint.
let address = body
.maybe(DIR_ADDRESS)
.parse_args_as_str::<net::SocketAddrV4>()?;
// check crosscert
{
let crosscert = body.get_required(DIR_KEY_CROSSCERT)?;
let mut tag = crosscert.get_obj_tag().unwrap();
// we are required to support both.
if tag != "ID SIGNATURE" && tag != "SIGNATURE" {
tag = "ID SIGNATURE";
}
let sig = crosscert.get_obj(tag)?;
let signed = identity_key.to_rsa_identity();
// TODO: we need to accept prefixes here. COMPAT BLOCKER.
let verified = signing_key.verify(signed.as_bytes(), &sig);
if verified.is_err() {
return Err(Error::BadSignature(crosscert.pos()));
}
}
// check the signature
{
let signature = body.get_required(DIR_KEY_CERTIFICATION)?;
let sig = signature.get_obj("SIGNATURE")?;
let mut sha1 = d::Sha1::new();
let s = reader.str();
let start_offset = body.first_item().unwrap().offset_in(s).unwrap();
let end_offset = body.last_item().unwrap().offset_in(s).unwrap();
let end_offset = end_offset + "dir-key-certification\n".len();
sha1.input(&s[start_offset..end_offset]);
let sha1 = sha1.result();
// TODO: we need to accept prefixes here. COMPAT BLOCKER.
let verified = identity_key.verify(&sha1, &sig);
if verified.is_err() {
return Err(Error::BadSignature(signature.pos()));
}
}
Ok(AuthCert {
address,
identity_key,
signing_key,
published,
expires,
})
}
}
#[cfg(test)]
mod test {
use super::*;
const TESTDATA: &str = include_str!("../testdata/authcert1.txt");
#[test]
fn parse_one() -> Result<()> {
let rd = AuthCert::parse(TESTDATA)?;
Ok(())
}
}

45
tor-netdoc/testdata/authcert1.txt vendored Normal file
View File

@ -0,0 +1,45 @@
dir-key-certificate-version 3
fingerprint ED03BB616EB2F60BEC80151114BB25CEF515B226
dir-key-published 2020-04-18 08:36:57
dir-key-expires 2021-04-18 08:36:57
dir-identity-key
-----BEGIN RSA PUBLIC KEY-----
MIIBigKCAYEA1d6uTRiqdMp4BHBYIHKR6NB599Z1Bqw4TbOVkM2N1aSA4V/L/hKI
nl6m/2LL/UAS+E3NCFX0dhw2+D7r7BTJyfGwz0H2MR6Py5/rCMAnPl20wCjXk2qY
ACQa0rJvIqXobwGnDlvxn4ezsj0IEY/FEb61zHnnPHf6d3uyFR1QT06qEOQyYzML
76f/Lud8MUt+8KzsdnadAPL8okNvcS/nqa2bWbbGhC8S8rtDpPg5BhX2ikXa88RM
QdrrackdppB2ttHlq9+iH3c8Wyp7bvdH8uhv410W7RnIE4P+KIxt3L0gqkxCjjyh
mn9ONcdgNOKe31q2cdW5LOPSIK+I5/VTjYjICza7Euyg03drpoBMGLuuJZY6FXEV
auIBncWe+So8FMxqU/fwo5xm6x085U1MwXUmi4XDYpr/kau6ytPnzzw9J++4W9iC
em5Jp0vaxrDnPdphqT0FWsBAwsZFL7nZRnmUlTgGsXUa0oSM9/MErDwzELh/NwG4
DNyyzRG8iP61AgMBAAE=
-----END RSA PUBLIC KEY-----
dir-signing-key
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA1OQgYH7drIJk2IjPiaeveOYZ2yHbWkZxSXeXYUgmMW8T4hNv1l7R
K767ckqmwhTZzrMKKAU3eMPaJbh82ySiRrpCdybhfGC1B+0m2MY3eqFPYR3BL3p+
Z62gf9BOQiJaC4QzHjRzc1kPQUEMEYU+Qu6aNOladxXt3bZRsGPhK/Oli4xdzl79
JoHR1Ka6At72ZesrpkUgV39NZZhJhYoQ+TA/vZNL6KGkYdvl178MEsKjKBxj3N0o
939VFgRiU89/epB8Fe0ve68KrEyb4wkuwXPhWIGuvF1Ttcc9vFRWhBZVEJJtjKIC
8uBvqvDaNZUMFivzaihoAGg3uLObYcWysQIDAQAB
-----END RSA PUBLIC KEY-----
dir-key-crosscert
-----BEGIN ID SIGNATURE-----
uog+UgmIJ9IrLrlIZtFbZT7rigP6TPB1iffPdSWrq0Xs1BsNBIsn1NMIFa4wGW/8
aCR8x+fCcTMTjsx7AqQkdgHRs0uxRarD8+KILnT4UgM0Jxje9Grl46nGs9GSogzA
8C6r4Xzs0vIaPG6i+K2iaAzGtPUpk2c29YtcCFMQb2iKjyNtQzODGibxQ9xlTb4O
1rYz+fn8qttyGVQQ7IQcnKFtyIJlNftKTZsXv45B/SWV4Y4o7Tujvb4LVtCM8BsU
JINmV4659EbmCOBNwb8B1d7Y7eBQurh4uUWujaDzuQTfIazMT7iP0MVSfWJRUZbj
3oQxOhdg0QnHhCNVvpkf3g==
-----END ID SIGNATURE-----
dir-key-certification
-----BEGIN SIGNATURE-----
Cskr5pUy1vQ1ghYcspoZM1NH8cLPjT9ouzkJ1vCBNBTW9BpE7J/5e1uCq7+83thv
HLW1HmG19o9x1marpqidEgMqOXjKcZqADE00TlVPefFKmXuQDSYO2tCu+wGA5HaL
nhDISUEG7md5uWP8cwbR5hZyL8ld0hnOFTt/mx5hwlZCkK/1iqNt1Jj7XOZY+K2P
EK/1l+guPbPtOO+vlXuaNY0PrZyeoNzyu4o7cmqmDsCV5e8oCVw9H9sz0e8cCpTQ
qrULkT5o/C5/hTqgMVMIRcTq8Y/w4epXj1SqM/F2MMu53wbF6aZqdR7btX2CBDRh
mmqDlFXZBZrZ1pksW1t/mb/OBMbsFe84Bp7pVcIW0vdaSbGOdLrE3QpaPRAiahbK
0YKksJlXbs756OuF8BJltqPHFnJZ+95L57hGE6mVBZ5fY9w2fhxmjjR3ZbFweBk0
u1dye5L906G5pseb3OkK1EybE2X7jWk7KZSs9oynYEaolZkbmn1qm36YAsZmJxCR
-----END SIGNATURE-----