formats
formats copied to clipboard
[question] how to handle application tags that preceed other values
I'm currently in the process of writing a kerberos encoder/decoder using this DER crate. In https://www.rfc-editor.org/rfc/rfc4120#section-5.4.1 the specification is:
AS-REQ ::= [APPLICATION 10] KDC-REQ
TGS-REQ ::= [APPLICATION 12] KDC-REQ
KDC-REQ ::= SEQUENCE {
-- NOTE: first tag is [1], not [0]
pvno [1] INTEGER (5) ,
msg-type [2] INTEGER (10 -- AS -- | 12 -- TGS --),
padata [3] SEQUENCE OF PA-DATA OPTIONAL
-- NOTE: not empty --,
req-body [4] KDC-REQ-BODY
}
For an example: https://asn1.jsteel.dev/#aoGyMIGvoQMCAQWiAwIBCqMaMBgwCqEEAgIAlqICBAAwCqEEAgIAlaICBACkgYYwgYOgBwMFAAAAABChFDASoAMCAQGhCzAJGwd3aWxsaWFtogsbCUtLRENQLkRFVqMeMBygAwIBAqEVMBMbBmtyYnRndBsJS0tEQ1AuREVWpREYDzIwMjQwNDE3MDQxNTQ5WqcGAgR_vaeuqBowGAIBEgIBEQIBFAIBEwIBEAIBFwIBGQIBGg
The problem I'm having is I can't see how I would handle this with der_derive. I can't map "application tags" to an enum of potential inner variants.
And from the "reader" types if I implemented it manually, I see there is https://docs.rs/der/0.7.9/der/trait.Reader.html#method.peek_tag but not "pop_tag". I probably need to try and experiment a bit more, but do you have any advice on how to decode this "nicely"?
PS: Would this crate be opposed to some extra additions that have some kerberos or LDAP specific use cases? For example, Kerberos needs an IA5String that has a GeneralString Tag so that would be a KerberosString type.
Thanks!
If you're running into an limitation of custom derive you may just need to write the trait impls manually. Several crates in this repo do that as well to avoid a hard dependency on a custom derive stack, see e.g. pkcs8
.
Since we don't have first-class support for fields with application tags, you'll just need to decode the Tag
, introspect it, and then determine how to decode the value, similar to how context-specific tags are handled.
For example, Kerberos needs an IA5String that has a GeneralString Tag so that would be a KerberosString type.
It seems like you should be able to define your own Ia5String
newtype for this, which decodes the tag, checks that it's GeneralString
, and then uses the DecodeValue
impl on Ia5String
.
For example, Kerberos needs an IA5String that has a GeneralString Tag so that would be a KerberosString type.
It seems like you should be able to define your own
Ia5String
newtype for this, which decodes the tag, checks that it'sGeneralString
, and then uses theDecodeValue
impl onIa5String
.
The problem here is some of your (very nice :) ) types like StrOwned and StrRef are private, and not being able to modify "Tag" to contain GeneralString as an option makes derived types not possible.
Which probably means at some point I have to give up and manual implement all my Decode's, but I was hoping to use the Derive macros :)
This is why I thought to ask about including these to this library, since it pretty easy to use/maintain KerberosString and KerberosTime since they are effectively copy-pastes of Ia5String and GeneralisedTime with minor changes (tag is GeneralString on KString and time can't have fractional components on KTime.).
The alternative is I externally re-invent both, which Im happy to do if you don't want to include these. But I thought I'd ask :)
EDIT: Would it be possible to expose some of your inner types like StrOwned and StrRef for users?
Instead of copy/pasting Ia5String
, you can make a KerberosString
newtype like struct KerberosString(Ia5String)
.
You will have to use handwritten trait impls, but that's what we'd do for a custom string type in der
anyway. It's pretty much unavoidable.
The tag is just:
impl FixedTag for KerberosString {
const TAG: Tag = Tag::GeneralString;
}
You can forward the DecodeValue
/EncodeValue
impls to the inner Ia5String
, and that's pretty much all you need.
EDIT: Would it be possible to expose some of your inner types like StrOwned and StrRef for users?
They're deliberately unexposed so we can make breaking changes to them easily, although perhaps they're at a point we can consider exposing them.
Yep, I'll do some more toying about for a bit. Thanks for your advice mate, it's really appreciated.
Yeah, I'm stuck. I don't actually know how I would proceed to decode that structure. I can't get context specific tagging to work on the pvno value once I have consumed the application tag from the sequence. I would really appreciate any advice you have on how to structure this, because I've been reading the pkcs8 and source code and can't work out how the pieces are meant to fit together. Any advice or support would be wonderful on how to use this better.
AS-REQ ::= [APPLICATION 10] KDC-REQ
KDC-REQ ::= SEQUENCE {
-- NOTE: first tag is [1], not [0]
pvno [1] INTEGER (5) ,
msg-type [2] INTEGER (10 -- AS -- | 12 -- TGS --),
padata [3] SEQUENCE OF PA-DATA OPTIONAL
-- NOTE: not empty --,
req-body [4] KDC-REQ-BODY
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct KdcReq {
pub pvno: Int,
}
impl<'a> ::der::Decode<'a> for KdcReq {
fn decode<R: ::der::Reader<'a>>(reader: &mut R) -> ::der::Result<Self> {
// =================
// This fails with: Unable to decode as_req: Error { kind: Length { tag: Tag(0x81: CONTEXT-SPECIFIC [1] (primitive)) }, position: Some(Length(3)) }
let pvno = reader
.context_specific(der::TagNumber::new(1), der::TagMode::Explicit)?
.ok_or_else(|| {
der::Error::new(der::ErrorKind::Failed, der::Length::ZERO )
})?;
Ok(KdcReq {
pvno
})
}
}
pub enum KerbMessage {
AsReq(KdcReq),
}
impl<'a> ::der::Decode<'a> for KerbMessage {
fn decode<R: ::der::Reader<'a>>(reader: &mut R) -> ::der::Result<Self> {
let tag_as_req: der::Tag = der::TagNumber::N10.application(true);
// let tag: der::Tag = reader.peek_tag()?;
let tag: der::Tag = reader.decode()?;
assert_eq!(tag, tag_as_req);
match tag {
tag_as_req => {
let kdc_req: KdcReq = reader
.decode()?;
Ok(KerbMessage::AsReq(kdc_req))
}
_ => unimplemented!(),
}
}
}
Can you give me a minimal example in hex along with the minimal schema you're having trouble decoding?
https://asn1.jsteel.dev/#aoGyMIGvoQMCAQWiAwIBCqMaMBgwCqEEAgIAlqICBAAwCqEEAgIAlaICBACkgYYwgYOgBwMFAAAAABChFDASoAMCAQGhCzAJGwd3aWxsaWFtogsbCUtLRENQLkRFVqMeMBygAwIBAqEVMBMbBmtyYnRndBsJS0tEQ1AuREVWpREYDzIwMjQwNDE3MDQxNTQ5WqcGAgR_vaeuqBowGAIBEgIBEQIBFAIBEwIBEAIBFwIBGQIBGg
Base64 URL Safe: aoGyMIGvoQMCAQWiAwIBCqMaMBgwCqEEAgIAlqICBAAwCqEEAgIAlaICBACkgYYwgYOgBwMFAAAAABChFDASoAMCAQGhCzAJGwd3aWxsaWFtogsbCUtLRENQLkRFVqMeMBygAwIBAqEVMBMbBmtyYnRndBsJS0tEQ1AuREVWpREYDzIwMjQwNDE3M DQxNTQ5WqcGAgR_vaeuqBowGAIBEgIBEQIBFAIBEwIBEAIBFwIBGQIBGg
It's the smallest example I have here, but should be the "problem structure" :)
From what I can tell, your code example is failing because you're not decoding the outer SEQUENCE
tag first in the Decode
impl on KdcReq
. That said...
You should be able to use Sequence
custom derive for KdcReq
, using the context_specific
attribute for each of the fields: https://docs.rs/der_derive/latest/der_derive/#asn1context_specific---attribute-context-specific-support
Here's an example, though it appears you'd want it to be EXPLICIT
: https://github.com/RustCrypto/formats/blob/8ae3e29e16bcbecd78dc08041746479414db7f45/x509-tsp/src/lib.rs#L135-L138
You only need the handwritten Decode
impl on KerbMessage
, which it may make sense to model as a Choice
.
Hopefully we've answered your questions
You have thank you!