1
+ use anyhow:: Context ;
2
+ use camino:: Utf8Path ;
1
3
use device_id:: DeviceIdError ;
2
4
use rcgen:: Certificate ;
3
5
use rcgen:: CertificateParams ;
@@ -6,8 +8,11 @@ use sha1::Digest;
6
8
use sha1:: Sha1 ;
7
9
use std:: path:: Path ;
8
10
use std:: path:: PathBuf ;
11
+ use tedge_p11_server:: CryptokiConfig ;
9
12
use time:: Duration ;
10
13
use time:: OffsetDateTime ;
14
+ use x509_parser:: oid_registry;
15
+ use x509_parser:: public_key:: PublicKey ;
11
16
pub use zeroize:: Zeroizing ;
12
17
#[ cfg( feature = "reqwest" ) ]
13
18
mod cloud_root_certificate;
@@ -140,11 +145,110 @@ pub enum ValidityStatus {
140
145
NotValidYet { valid_in : std:: time:: Duration } ,
141
146
}
142
147
148
+ #[ derive( Debug , Clone ) ]
143
149
pub enum KeyKind {
144
150
/// Create a new key
145
151
New ,
146
152
/// Reuse the existing PEM-encoded key pair
147
153
Reuse { keypair_pem : String } ,
154
+ /// Reuse the keypair where we can't read the private key because it's on the HSM.
155
+ ReuseRemote ( RemoteKeyPair ) ,
156
+ }
157
+
158
+ impl KeyKind {
159
+ pub fn from_cryptoki_and_existing_cert (
160
+ cryptoki_config : CryptokiConfig ,
161
+ current_cert : & Utf8Path ,
162
+ ) -> Result < Self , CertificateError > {
163
+ let cert = PemCertificate :: from_pem_file ( current_cert) ?;
164
+ let cert = PemCertificate :: extract_certificate ( & cert. pem ) ?;
165
+ let public_key_raw = cert. public_key ( ) . subject_public_key . data . to_vec ( ) ;
166
+
167
+ // map public key to signature identifier (some keys support many types of signatures, on
168
+ // our side we only do P256/P384/RSA2048 with a single type of signature each)
169
+ // for P256/P384, former has only SHA256 and latter only SHA384 signature, so no questions there
170
+ // but for RSA, AFAIK we can use SHA256 with all of them. RSA_PSS also isn't supported by
171
+ // rcgen, but we're free to use regular RSA_PKCS1_SHA256
172
+ let public_key = cert
173
+ . public_key ( )
174
+ . parsed ( )
175
+ . context ( "Failed to read public key from the certificate" ) ?;
176
+ let signature_algorithm = match public_key {
177
+ PublicKey :: EC ( ec) => match ec. key_size ( ) {
178
+ 256 => oid_registry:: OID_SIG_ECDSA_WITH_SHA256 ,
179
+ 384 => oid_registry:: OID_SIG_ECDSA_WITH_SHA384 ,
180
+ // P521 (size 528 reported by key_size() is not yet supported by rcgen)
181
+ // https://github.com/rustls/rcgen/issues/60
182
+ _ => {
183
+ return Err ( anyhow:: anyhow!( "Unsupported public key. Only P256/P384/RSA2048/RSA3072/RSA4096 are supported for certificate renewal" ) . into ( ) ) ;
184
+ }
185
+ } ,
186
+ PublicKey :: RSA ( _) => oid_registry:: OID_PKCS1_SHA256WITHRSA ,
187
+ _ => return Err ( anyhow:: anyhow!( "Unsupported public key. Only P256/P384/RSA2048/RSA3072/RSA4096 are supported for certificate renewal" ) . into ( ) )
188
+ } ;
189
+ let signature_algorithm: Vec < u64 > = signature_algorithm
190
+ . iter ( )
191
+ . map ( |i| i. collect ( ) )
192
+ . unwrap_or_default ( ) ;
193
+ let algorithm = rcgen:: SignatureAlgorithm :: from_oid ( & signature_algorithm) ?;
194
+
195
+ Ok ( Self :: ReuseRemote ( RemoteKeyPair {
196
+ cryptoki_config,
197
+ public_key_raw,
198
+ algorithm,
199
+ } ) )
200
+ }
201
+ }
202
+
203
+ /// A key pair using a remote private key.
204
+ ///
205
+ /// To generate a CSR we need:
206
+ /// - the public key, because the public key is a part of the certificate (subject public key info)
207
+ /// - the private key, to sign the CSR to prove that the public key is ours
208
+ ///
209
+ /// With private key in the HSM, we can't access its private parts, but we can still use it to sign.
210
+ /// For the public key, instead of deriving it from the private key, which needs some additions to
211
+ /// our PKCS11 code, we can just reuse the SPKI section from an existing certificate if we have it,
212
+ /// i.e. we're renewing and not getting a brand new cert.
213
+ ///
214
+ /// An alternative, looking at how it's done in gnutls (_pkcs11_privkey_get_pubkey and
215
+ /// pkcs11_read_pubkey functions), seems to be:
216
+ /// - if the key is RSA, a public key can be trivially derived from the public properties of PKCS11
217
+ /// private key object
218
+ /// - for EC key, it should also be possible to derive public from private
219
+ /// - if that fails, a public key object may also be present on the token
220
+ #[ derive( Debug , Clone ) ]
221
+ pub struct RemoteKeyPair {
222
+ cryptoki_config : CryptokiConfig ,
223
+ public_key_raw : Vec < u8 > ,
224
+ algorithm : & ' static rcgen:: SignatureAlgorithm ,
225
+ }
226
+
227
+ impl RemoteKeyPair {
228
+ pub fn to_key_pair ( & self ) -> Result < KeyPair , CertificateError > {
229
+ Ok ( KeyPair :: from_remote ( Box :: new ( self . clone ( ) ) ) ?)
230
+ }
231
+ }
232
+
233
+ impl rcgen:: RemoteKeyPair for RemoteKeyPair {
234
+ fn public_key ( & self ) -> & [ u8 ] {
235
+ & self . public_key_raw
236
+ }
237
+
238
+ fn sign ( & self , msg : & [ u8 ] ) -> Result < Vec < u8 > , rcgen:: Error > {
239
+ // the error here is not PEM-related, but we need to return a foreign error type, and there
240
+ // are no other better variants that could let us return context, so we'll have to use this
241
+ // until `rcgen::Error::RemoteKeyError` can take a parameter
242
+ let signer = tedge_p11_server:: signing_key ( self . cryptoki_config . clone ( ) )
243
+ . map_err ( |e| rcgen:: Error :: PemError ( e. to_string ( ) ) ) ?;
244
+ signer
245
+ . sign ( msg)
246
+ . map_err ( |e| rcgen:: Error :: PemError ( e. to_string ( ) ) )
247
+ }
248
+
249
+ fn algorithm ( & self ) -> & ' static rcgen:: SignatureAlgorithm {
250
+ self . algorithm
251
+ }
148
252
}
149
253
150
254
pub struct KeyCertPair {
@@ -176,7 +280,9 @@ impl KeyCertPair {
176
280
// as rcgen library will not parse it for certificate signing request
177
281
let params = Self :: create_csr_parameters ( config, id, key_kind) ?;
178
282
Ok ( KeyCertPair {
179
- certificate : Zeroizing :: new ( Certificate :: from_params ( params) ?) ,
283
+ certificate : Zeroizing :: new (
284
+ Certificate :: from_params ( params) . context ( "Failed to create CSR" ) ?,
285
+ ) ,
180
286
} )
181
287
}
182
288
@@ -215,15 +321,23 @@ impl KeyCertPair {
215
321
let mut params = CertificateParams :: default ( ) ;
216
322
params. distinguished_name = distinguished_name;
217
323
218
- if let KeyKind :: Reuse { keypair_pem } = key_kind {
219
- // Use the same signing algorithm as the existing key
220
- // Failing to do so leads to an error telling the algorithm is not compatible
221
- let key_pair = KeyPair :: from_pem ( keypair_pem) ?;
222
- params. alg = key_pair. algorithm ( ) ;
223
- params. key_pair = Some ( key_pair) ;
224
- } else {
225
- // ECDSA signing using the P-256 curves and SHA-256 hashing as per RFC 5758
226
- params. alg = & rcgen:: PKCS_ECDSA_P256_SHA256 ;
324
+ match key_kind {
325
+ KeyKind :: New => {
326
+ // ECDSA signing using the P-256 curves and SHA-256 hashing as per RFC 5758
327
+ params. alg = & rcgen:: PKCS_ECDSA_P256_SHA256 ;
328
+ }
329
+ KeyKind :: Reuse { keypair_pem } => {
330
+ // Use the same signing algorithm as the existing key
331
+ // Failing to do so leads to an error telling the algorithm is not compatible
332
+ let key_pair = KeyPair :: from_pem ( keypair_pem) ?;
333
+ params. alg = key_pair. algorithm ( ) ;
334
+ params. key_pair = Some ( key_pair) ;
335
+ }
336
+ KeyKind :: ReuseRemote ( key_pair) => {
337
+ let key_pair = key_pair. to_key_pair ( ) ?;
338
+ params. alg = key_pair. algorithm ( ) ;
339
+ params. key_pair = Some ( key_pair)
340
+ }
227
341
}
228
342
229
343
Ok ( params)
0 commit comments