1
1
//! Functions for extracting public key from X509 and C509 certificates with additional
2
2
//! verification.
3
3
4
- use anyhow:: { anyhow, Context } ;
4
+ use std:: borrow:: Cow ;
5
+
5
6
use 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 } ;
7
9
use oid_registry:: { Oid , OID_SIG_ED25519 } ;
8
10
use 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
+ }
10
45
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
+ }
17
84
}
18
85
19
86
/// Returns `VerifyingKey` from the given X509 certificate.
@@ -22,24 +89,21 @@ pub struct SignatureAlgoError {
22
89
///
23
90
/// Returns an error if the signature algorithm is not supported and
24
91
/// 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
37
98
. tbs_certificate
38
99
. subject_public_key_info
39
- . subject_public_key
100
+ . subject_public_key ;
101
+ let public_key_bytes = public_key
40
102
. 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)
43
107
}
44
108
45
109
/// Returns `VerifyingKey` from the given C509 certificate.
@@ -48,47 +112,65 @@ pub fn x509_key(cert: &X509Certificate) -> anyhow::Result<VerifyingKey> {
48
112
///
49
113
/// Returns an error if the signature algorithm is not supported and
50
114
/// 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 > {
52
118
let oid = cert
53
119
. tbs_cert ( )
54
120
. subject_public_key_algorithm ( )
55
121
. algo_identifier ( )
56
122
. oid ( ) ;
57
123
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 )
60
126
}
61
127
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 > {
64
131
// 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
+ } )
69
138
}
70
- Ok ( ( ) )
71
139
}
72
140
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( ) ) ;
79
171
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 ) ;
85
175
}
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" )
94
176
}
0 commit comments