11//! Functions for extracting public key from X509 and C509 certificates with additional
22//! verification.
33
4- use anyhow:: { anyhow, Context } ;
4+ use std:: borrow:: Cow ;
5+
56use c509_certificate:: c509:: C509 ;
6- use ed25519_dalek:: { VerifyingKey , PUBLIC_KEY_LENGTH } ;
7+ use catalyst_types:: problem_report:: ProblemReport ;
8+ use ed25519_dalek:: { SignatureError , VerifyingKey , PUBLIC_KEY_LENGTH } ;
79use oid_registry:: { Oid , OID_SIG_ED25519 } ;
810use thiserror:: Error ;
9- use x509_cert:: Certificate as X509Certificate ;
11+ use x509_cert:: { spki, Certificate as X509Certificate } ;
12+
13+ /// Common error type.
14+ #[ derive( Debug , Error ) ]
15+ pub enum Error {
16+ /// Unsupported signature algorithms.
17+ #[ error( "Unsupported signature algorithm (oid: {oid})" ) ]
18+ UnsupportedSignatureAlgo {
19+ /// An OID of unsupported signature algorithm.
20+ oid : Oid < ' static > ,
21+ } ,
22+ /// Public key has invalid length.
23+ #[ error(
24+ "Invalid public key length (found {bytes} bytes, but expected {PUBLIC_KEY_LENGTH} bytes)"
25+ ) ]
26+ InvalidPublicKeyLength {
27+ /// Number of bytes found.
28+ bytes : usize ,
29+ } ,
30+ /// Public key is stored in a bit string, where number of unused bits is *not* equal
31+ /// to zero.
32+ #[ error( "Invalid public key is not octet aligned (found {bits} bits)" ) ]
33+ PublicKeyIsNotOctetAligned {
34+ /// Number of bits found.
35+ bits : usize ,
36+ } ,
37+ /// Public key doesn't pass [`ed25519_dalek`] constraint check.
38+ #[ error( "Invalid public key ({source})" ) ]
39+ PublicKeyIsNotEd25519 {
40+ /// Underlying [`ed25519_dalek`] error.
41+ #[ from]
42+ source : SignatureError ,
43+ } ,
44+ }
1045
11- /// Error type for unsupported signature algorithms.
12- #[ derive( Error , Debug ) ]
13- #[ error( "Unsupported signature algorithm: {oid}" ) ]
14- pub struct SignatureAlgoError {
15- /// An OID of unsupported signature algorithm.
16- oid : String ,
46+ impl Error {
47+ /// Shortcut function to report `Self` into a [`ProblemReport`].
48+ pub fn report_problem ( & self , context : & str , report : & ProblemReport ) {
49+ match self {
50+ Error :: UnsupportedSignatureAlgo { oid } => {
51+ report. invalid_value (
52+ "subject_public_key_algorithm" ,
53+ & oid. to_id_string ( ) ,
54+ "Currently the only supported signature algorithm is ED25519" ,
55+ context,
56+ ) ;
57+ } ,
58+ Error :: InvalidPublicKeyLength { bytes } => {
59+ report. invalid_value (
60+ "subject_public_key" ,
61+ & format ! ( "{bytes} bytes" ) ,
62+ & format ! ( "Must be {PUBLIC_KEY_LENGTH} bytes long" ) ,
63+ context,
64+ ) ;
65+ } ,
66+ Error :: PublicKeyIsNotOctetAligned { bits } => {
67+ report. invalid_value (
68+ "subject_public_key" ,
69+ & format ! ( "{bits} bits" ) ,
70+ "Bit string must be octet aligned having no unused bits" ,
71+ context,
72+ ) ;
73+ } ,
74+ Error :: PublicKeyIsNotEd25519 { source } => {
75+ report. invalid_value (
76+ "subject_public_key" ,
77+ & source. to_string ( ) ,
78+ "Must be an Ed25519 public key" ,
79+ context,
80+ ) ;
81+ } ,
82+ }
83+ }
1784}
1885
1986/// Returns `VerifyingKey` from the given X509 certificate.
@@ -22,24 +89,21 @@ pub struct SignatureAlgoError {
2289///
2390/// Returns an error if the signature algorithm is not supported and
2491/// the public key cannot be extracted.
25- pub fn x509_key ( cert : & X509Certificate ) -> anyhow:: Result < VerifyingKey > {
26- let oid: Oid = cert
27- . tbs_certificate
28- . subject_public_key_info
29- . algorithm
30- . oid
31- . to_string ( )
32- . parse ( )
33- // `Context` cannot be used here because `OidParseError` doesn't implement `std::Error`.
34- . map_err ( |e| anyhow ! ( "Invalid signature algorithm OID: {e:?}" ) ) ?;
35- check_signature_algorithm ( & oid) ?;
36- let extended_public_key = cert
92+ ///
93+ /// Returns an error if public key has unexpected value.
94+ pub fn x509_key ( cert : & X509Certificate ) -> Result < VerifyingKey , Error > {
95+ let oid = & cert. tbs_certificate . subject_public_key_info . algorithm . oid ;
96+ check_signature_algorithm ( & spki_oid_as_asn1_rs_oid ( oid) ) ?;
97+ let public_key = & cert
3798 . tbs_certificate
3899 . subject_public_key_info
39- . subject_public_key
100+ . subject_public_key ;
101+ let public_key_bytes = public_key
40102 . as_bytes ( )
41- . context ( "Invalid subject_public_key value (has unused bits)" ) ?;
42- verifying_key ( extended_public_key) . context ( "Unable to get verifying key from X509 certificate" )
103+ . ok_or ( Error :: PublicKeyIsNotOctetAligned {
104+ bits : public_key. bit_len ( ) ,
105+ } ) ?;
106+ verifying_key ( public_key_bytes)
43107}
44108
45109/// Returns `VerifyingKey` from the given C509 certificate.
@@ -48,47 +112,65 @@ pub fn x509_key(cert: &X509Certificate) -> anyhow::Result<VerifyingKey> {
48112///
49113/// Returns an error if the signature algorithm is not supported and
50114/// the public key cannot be extracted.
51- pub fn c509_key ( cert : & C509 ) -> anyhow:: Result < VerifyingKey > {
115+ ///
116+ /// Returns an error if public key has unexpected value.
117+ pub fn c509_key ( cert : & C509 ) -> Result < VerifyingKey , Error > {
52118 let oid = cert
53119 . tbs_cert ( )
54120 . subject_public_key_algorithm ( )
55121 . algo_identifier ( )
56122 . oid ( ) ;
57123 check_signature_algorithm ( oid) ?;
58- verifying_key ( cert. tbs_cert ( ) . subject_public_key ( ) )
59- . context ( "Unable to get verifying key from C509 certificate" )
124+ let public_key = cert. tbs_cert ( ) . subject_public_key ( ) ;
125+ verifying_key ( public_key )
60126}
61127
62- /// Checks that the signature algorithm is supported.
63- fn check_signature_algorithm ( oid : & Oid ) -> Result < ( ) , SignatureAlgoError > {
128+ /// Checks that the signature algorithm with the given [`spki::ObjectIdentifier`] is
129+ /// supported.
130+ fn check_signature_algorithm ( oid : & Oid ) -> Result < ( ) , Error > {
64131 // Currently the only supported signature algorithm is ED25519.
65- if * oid != OID_SIG_ED25519 {
66- return Err ( SignatureAlgoError {
67- oid : oid. to_id_string ( ) ,
68- } ) ;
132+ if * oid == OID_SIG_ED25519 {
133+ Ok ( ( ) )
134+ } else {
135+ Err ( Error :: UnsupportedSignatureAlgo {
136+ oid : oid. to_owned ( ) ,
137+ } )
69138 }
70- Ok ( ( ) )
71139}
72140
73- // TODO: The very similar logic exists in the `rbac-registration` crate. It should be
74- // moved somewhere and reused. See https://github.com/input-output-hk/catalyst-voices/issues/1952
75- /// Creates `VerifyingKey` from the given extended public key.
76- fn verifying_key ( extended_public_key : & [ u8 ] ) -> anyhow:: Result < VerifyingKey > {
77- /// An extender public key length in bytes.
78- const EXTENDED_PUBLIC_KEY_LENGTH : usize = 64 ;
141+ /// Converts [`spki::ObjectIdentifier`] ref to an [`Oid`].
142+ fn spki_oid_as_asn1_rs_oid ( oid : & ' _ spki:: ObjectIdentifier ) -> Oid < ' _ > {
143+ // Note that this conversion always succeeds as both crates omit header.
144+ Oid :: new ( Cow :: Borrowed ( oid. as_bytes ( ) ) )
145+ }
146+
147+ /// Creates [`VerifyingKey`] from the first 32 bytes in a slice.
148+ /// Since only prefix bytes are used, both extended and common public keys are supported
149+ /// here.
150+ fn verifying_key ( public_key : & [ u8 ] ) -> Result < VerifyingKey , Error > {
151+ public_key
152+ // TODO: replace with checked `[u8; 32]` conversion once we only support common ed25119.
153+ . first_chunk ( )
154+ // Public key is too short.
155+ . ok_or ( Error :: InvalidPublicKeyLength {
156+ bytes : public_key. len ( ) ,
157+ } )
158+ . and_then ( |bytes| VerifyingKey :: from_bytes ( bytes) . map_err ( Error :: from) )
159+ }
160+
161+ #[ cfg( test) ]
162+ mod tests {
163+ use oid_registry:: { asn1_rs, OID_SIG_ED25519 } ;
164+ use x509_cert:: spki;
165+
166+ #[ test]
167+ fn spki_oid_as_asn1_rs_oid ( ) {
168+ let spki_oid = spki:: ObjectIdentifier :: new_unwrap ( "1.3.101.112" ) ;
169+ let asn1_rs_oid = asn1_rs:: oid!( 1.3 . 101 . 112 ) ;
170+ assert_eq ! ( spki_oid. to_string( ) , asn1_rs_oid. to_id_string( ) ) ;
79171
80- if extended_public_key. len ( ) != EXTENDED_PUBLIC_KEY_LENGTH {
81- return Err ( anyhow ! (
82- "Unexpected extended public key length in certificate: {}, expected {EXTENDED_PUBLIC_KEY_LENGTH}" ,
83- extended_public_key. len( )
84- ) ) ;
172+ let converted_spki_oid = super :: spki_oid_as_asn1_rs_oid ( & spki_oid) ;
173+ assert_eq ! ( converted_spki_oid. to_string( ) , asn1_rs_oid. to_id_string( ) ) ;
174+ assert_eq ! ( converted_spki_oid, OID_SIG_ED25519 ) ;
85175 }
86- // This should never fail because of the check above.
87- let public_key = extended_public_key
88- . get ( 0 ..PUBLIC_KEY_LENGTH )
89- . context ( "Unable to get public key part" ) ?;
90- let bytes: & [ u8 ; PUBLIC_KEY_LENGTH ] = public_key
91- . try_into ( )
92- . context ( "Invalid public key length in X509 certificate" ) ?;
93- VerifyingKey :: from_bytes ( bytes) . context ( "Invalid public key in X509 certificate" )
94176}
0 commit comments