netdoc: initial implementation of authority certs.
This commit is contained in:
parent
2d8021420f
commit
3b4c828cb7
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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-----
|
Loading…
Reference in New Issue