Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 75 additions & 26 deletions der/derive/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,33 @@ pub(crate) struct FieldAttrs {
/// Value of the `#[asn1(context_specific = "...")] attribute if provided.
pub context_specific: Option<TagNumber>,

/// Indicates name of function that supplies the default value, which will be used in cases
/// where encoding is omitted per DER and to omit the encoding per DER
pub default: Option<Path>,

/// Is this field "extensible", i.e. preceded by the `...` extensibility marker?
pub extensible: bool,

/// Is this field `OPTIONAL`?
pub optional: bool,

/// Tagging mode for this type: `EXPLICIT` or `IMPLICIT`, supplied as
/// `#[asn1(tag_mode = "...")]`.
///
/// Inherits from the type-level tagging mode if specified, or otherwise
/// defaults to `EXPLICIT`.
pub tag_mode: TagMode,

/// Indicates name of function that supplies the default value, which will be used in cases
/// where encoding is omitted per DER and to omit the encoding per DER
pub default: Option<Path>,
}

impl FieldAttrs {
/// Parse attributes from a struct field or enum variant.
pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> Self {
let mut asn1_type = None;
let mut context_specific = None;
let mut tag_mode = None;
let mut default = None;
let mut extensible = None;
let mut optional = None;
let mut tag_mode = None;

let mut parsed_attrs = Vec::new();
AttrNameValue::from_attributes(attrs, &mut parsed_attrs);
Expand All @@ -93,27 +101,43 @@ impl FieldAttrs {
}

context_specific = Some(tag_number);
// `type = "..."` attribute
} else if let Some(ty) = attr.parse_value("type") {
if asn1_type.is_some() {
abort!(attr.name, "duplicate ASN.1 `type` attribute: {}");
// `default` attribute
} else if attr.parse_value::<String>("default").is_some() {
if default.is_some() {
abort!(attr.name, "duplicate ASN.1 `default` attribute");
}

asn1_type = Some(ty);
default = Some(attr.value.parse().unwrap_or_else(|e| {
abort!(attr.value, "error parsing ASN.1 `default` attribute: {}", e)
}));
// `extensible` attribute
} else if let Some(ext) = attr.parse_value("extensible") {
if extensible.is_some() {
abort!(attr.name, "duplicate ASN.1 `extensible` attribute");
}

extensible = Some(ext);
// `optional` attribute
} else if let Some(opt) = attr.parse_value("optional") {
if optional.is_some() {
abort!(attr.name, "duplicate ASN.1 `optional` attribute");
}

optional = Some(opt);
// `tag_mode` attribute
} else if let Some(mode) = attr.parse_value("tag_mode") {
if tag_mode.is_some() {
abort!(attr.name, "duplicate ASN.1 `tag_mode` attribute");
}

tag_mode = Some(mode);
} else if attr.parse_value::<String>("default").is_some() {
if default.is_some() {
abort!(attr.name, "duplicate ASN.1 `default` attribute");
// `type = "..."` attribute
} else if let Some(ty) = attr.parse_value("type") {
if asn1_type.is_some() {
abort!(attr.name, "duplicate ASN.1 `type` attribute: {}");
}

default = Some(attr.value.parse().unwrap_or_else(|e| {
abort!(attr.value, "error parsing ASN.1 `default` attribute: {}", e)
}));
asn1_type = Some(ty);
} else {
abort!(
attr.name,
Expand All @@ -127,6 +151,8 @@ impl FieldAttrs {
asn1_type,
context_specific,
default,
extensible: extensible.unwrap_or_default(),
optional: optional.unwrap_or_default(),
tag_mode: tag_mode.unwrap_or(type_attrs.tag_mode),
}
}
Expand Down Expand Up @@ -158,21 +184,44 @@ impl FieldAttrs {

let context_specific = match self.tag_mode {
TagMode::Explicit => {
quote!(::der::asn1::ContextSpecific::<#type_params>::decode_explicit(decoder, #tag_number)?)
if self.extensible || self.optional {
quote! {
::der::asn1::ContextSpecific::<#type_params>::decode_explicit(
decoder,
#tag_number
)?
}
} else {
quote! {
match ::der::asn1::ContextSpecific::<#type_params>::decode(decoder)? {
field if field.tag_number == #tag_number => Some(field),
_ => None
}
}
}
}
TagMode::Implicit => {
quote!(::der::asn1::ContextSpecific::<#type_params>::decode_implicit(decoder, #tag_number)?)
quote! {
::der::asn1::ContextSpecific::<#type_params>::decode_implicit(
decoder,
#tag_number
)?
}
}
};

// TODO(tarcieri): better error handling?
quote! {
#context_specific.ok_or_else(|| {
der::Tag::ContextSpecific {
number: #tag_number,
constructed: false
}.value_error()
})?.value
if self.optional {
quote!(#context_specific.map(|cs| cs.value))
} else {
// TODO(tarcieri): better error handling?
quote! {
#context_specific.ok_or_else(|| {
der::Tag::ContextSpecific {
number: #tag_number,
constructed: false
}.value_error()
})?.value
}
}
} else {
self.asn1_type
Expand Down
4 changes: 4 additions & 0 deletions der/derive/src/choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ impl ChoiceVariant {
let ident = input.ident.clone();
let attrs = FieldAttrs::parse(&input.attrs, type_attrs);

if attrs.extensible {
abort!(&ident, "`extensible` is not allowed on CHOICE");
}

// Validate that variant is a 1-element tuple struct
match &input.fields {
// TODO(tarcieri): handle 0 bindings for ASN.1 NULL
Expand Down
15 changes: 15 additions & 0 deletions der/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@
//!
//! The value must be quoted and contain a number, e.g. `#[asn1(context_specific = "42"]`.
//!
//! ### `#[asn1(default = "...")` attribute: `DEFAULT` support
//!
//! This behaves like `serde_derive`'s `default` attribute, allowing you to
//! specify the path to a function which returns a default value.
//!
//! ### `#[asn1(extensible = "true")` attribute: support for `...` extensibility operator
//!
//! This attribute can be applied to the fields of `struct` types, and will
//! skip over unrecognized lower-numbered `CONTEXT-SPECIFIC` fields when
//! looking for a particular field of a struct.
//!
//! ### `#[asn1(optional = "true")` attribute: support for `OPTIONAL` fields
//!
//! This attribute explicitly annotates a field as `OPTIONAL`.
//!
//! ### `#[asn1(type = "...")]` attribute: ASN.1 type declaration
//!
//! This attribute can be used to specify the ASN.1 type for a particular
Expand Down
141 changes: 128 additions & 13 deletions der/derive/src/sequence.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Support for deriving the `Sequence` trait on structs for the purposes of
//! decoding/encoding ASN.1 `SEQUENCE` types as mapped to struct fields.

use crate::{FieldAttrs, TagMode, TypeAttrs};
use crate::{FieldAttrs, TypeAttrs};
use proc_macro2::TokenStream;
use proc_macro_error::abort;
use quote::quote;
Expand Down Expand Up @@ -130,11 +130,6 @@ impl SequenceField {
});

let attrs = FieldAttrs::parse(&field.attrs, type_attrs);
let field_type = field.ty.clone();

if attrs.tag_mode == TagMode::Implicit {
abort!(ident, "IMPLICIT tagging not supported for `Sequence`");
}

if attrs.asn1_type.is_some() && attrs.default.is_some() {
abort!(
Expand All @@ -146,7 +141,7 @@ impl SequenceField {
Self {
ident,
attrs,
field_type,
field_type: field.ty.clone(),
}
}

Expand All @@ -158,19 +153,25 @@ impl SequenceField {

let ident = &self.ident;
let ty = &self.field_type;
let decoder = self.attrs.decoder();

if self.attrs.asn1_type.is_some() {
let dec = self.attrs.decoder();
quote! {
let #ident = #dec.try_into()?;
if self.attrs.optional {
quote! {
let #ident = #decoder.map(TryInto::try_into).transpose()?;
}
} else {
quote! {
let #ident = #decoder.try_into()?;
}
}
} else if let Some(default) = &self.attrs.default {
quote! {
let #ident = decoder.decode::<Option<#ty>>()?.unwrap_or_else(#default);
}
} else {
quote! {
let #ident = decoder.decode()?;
let #ident = #decoder;
}
}
}
Expand All @@ -185,8 +186,21 @@ impl SequenceField {
let binding = quote!(&self.#ident);

if let Some(ty) = &self.attrs.asn1_type {
let encoder = ty.encoder(&binding);
quote!(&#encoder)
if self.attrs.optional {
let map_arg = quote!(field);
let encoder = ty.encoder(&map_arg);

// TODO(tarcieri): refactor this to get rid of `Result` type annotation
quote! {
#binding.as_ref().map(|#map_arg| {
let res: der::Result<_> = Ok(#encoder);
res
}).transpose()?
}
} else {
let encoder = ty.encoder(&binding);
quote!(&#encoder)
}
} else if let Some(default) = &self.attrs.default {
quote! {
&::der::asn1::OptionalRef(if #binding == &#default() {
Expand All @@ -211,6 +225,7 @@ mod tests {
#[test]
fn algorithm_identifier_example() {
let input = parse_quote! {
#[derive(Sequence)]
pub struct AlgorithmIdentifier<'a> {
pub algorithm: ObjectIdentifier,
pub parameters: Option<Any<'a>>,
Expand Down Expand Up @@ -239,6 +254,7 @@ mod tests {
#[test]
fn spki_example() {
let input = parse_quote! {
#[derive(Sequence)]
pub struct SubjectPublicKeyInfo<'a> {
pub algorithm: AlgorithmIdentifier<'a>,

Expand Down Expand Up @@ -267,4 +283,103 @@ mod tests {
assert_eq!(subject_public_key_field.attrs.context_specific, None);
assert_eq!(subject_public_key_field.attrs.tag_mode, TagMode::Explicit);
}

/// PKCS#8v2 `OneAsymmetricKey`.
///
/// ```text
/// OneAsymmetricKey ::= SEQUENCE {
/// version Version,
/// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
/// privateKey PrivateKey,
/// attributes [0] Attributes OPTIONAL,
/// ...,
/// [[2: publicKey [1] PublicKey OPTIONAL ]],
/// ...
/// }
///
/// Version ::= INTEGER { v1(0), v2(1) } (v1, ..., v2)
///
/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
///
/// PrivateKey ::= OCTET STRING
///
/// Attributes ::= SET OF Attribute
///
/// PublicKey ::= BIT STRING
/// ```
#[test]
fn pkcs8_example() {
let input = parse_quote! {
#[derive(Sequence)]
pub struct OneAsymmetricKey<'a> {
pub version: u8,
pub private_key_algorithm: AlgorithmIdentifier<'a>,
#[asn1(type = "OCTET STRING")]
pub private_key: &'a [u8],
#[asn1(context_specific = "0", extensible = "true", optional = "true")]
pub attributes: Option<SetOf<Any<'a>, 1>>,
#[asn1(
context_specific = "1",
extensible = "true",
optional = "true",
type = "BIT STRING"
)]
pub public_key: Option<&'a [u8]>,
}
};

let ir = DeriveSequence::new(input);
assert_eq!(ir.ident, "OneAsymmetricKey");
assert_eq!(ir.lifetime.unwrap().to_string(), "'a");
assert_eq!(ir.fields.len(), 5);

let version_field = &ir.fields[0];
assert_eq!(version_field.ident, "version");
assert_eq!(version_field.attrs.asn1_type, None);
assert_eq!(version_field.attrs.context_specific, None);
assert_eq!(version_field.attrs.extensible, false);
assert_eq!(version_field.attrs.optional, false);
assert_eq!(version_field.attrs.tag_mode, TagMode::Explicit);

let algorithm_field = &ir.fields[1];
assert_eq!(algorithm_field.ident, "private_key_algorithm");
assert_eq!(algorithm_field.attrs.asn1_type, None);
assert_eq!(algorithm_field.attrs.context_specific, None);
assert_eq!(algorithm_field.attrs.extensible, false);
assert_eq!(algorithm_field.attrs.optional, false);
assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit);

let private_key_field = &ir.fields[2];
assert_eq!(private_key_field.ident, "private_key");
assert_eq!(
private_key_field.attrs.asn1_type,
Some(Asn1Type::OctetString)
);
assert_eq!(private_key_field.attrs.context_specific, None);
assert_eq!(private_key_field.attrs.extensible, false);
assert_eq!(private_key_field.attrs.optional, false);
assert_eq!(private_key_field.attrs.tag_mode, TagMode::Explicit);

let attributes_field = &ir.fields[3];
assert_eq!(attributes_field.ident, "attributes");
assert_eq!(attributes_field.attrs.asn1_type, None);
assert_eq!(
attributes_field.attrs.context_specific,
Some("0".parse().unwrap())
);
assert_eq!(attributes_field.attrs.extensible, true);
assert_eq!(attributes_field.attrs.optional, true);
assert_eq!(attributes_field.attrs.tag_mode, TagMode::Explicit);

let public_key_field = &ir.fields[4];
assert_eq!(public_key_field.ident, "public_key");
assert_eq!(public_key_field.attrs.asn1_type, Some(Asn1Type::BitString));
assert_eq!(
public_key_field.attrs.context_specific,
Some("1".parse().unwrap())
);
assert_eq!(public_key_field.attrs.extensible, true);
assert_eq!(public_key_field.attrs.optional, true);
assert_eq!(public_key_field.attrs.tag_mode, TagMode::Explicit);
}
}
Loading