• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

namib-project / dcaf-rs / 10516465139

22 Aug 2024 10:16PM UTC coverage: 85.144% (+0.2%) from 84.95%
10516465139

Pull #24

github

web-flow
Merge 0da044a44 into d087ba7ef
Pull Request #24: Add support for AES-CCM

2185 of 4715 branches covered (46.34%)

229 of 266 new or added lines in 16 files covered. (86.09%)

1 existing line in 1 file now uncovered.

3158 of 3709 relevant lines covered (85.14%)

48.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.51
/src/token/cose/crypto_impl/openssl.rs
1
/*
2
 * Copyright (c) 2022-2024 The NAMIB Project Developers.
3
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
 * option. This file may not be copied, modified, or distributed
7
 * except according to those terms.
8
 *
9
 * SPDX-License-Identifier: MIT OR Apache-2.0
10
 */
11

12
use alloc::vec::Vec;
13
use ciborium::value::Value;
14
use coset::{iana, Algorithm};
15
use openssl::aes::{unwrap_key, wrap_key, AesKey};
16
use openssl::bn::BigNum;
17
use openssl::cipher::CipherRef;
18
use openssl::cipher_ctx::CipherCtx;
19
use openssl::ec::{EcGroup, EcKey};
20
use openssl::ecdsa::EcdsaSig;
21
use openssl::error::ErrorStack;
22
use openssl::hash::MessageDigest;
23
use openssl::nid::Nid;
24
use openssl::pkey::{PKey, Private, Public};
25
use openssl::sign::{Signer, Verifier};
26
use strum_macros::Display;
27

28
use crate::error::CoseCipherError;
29
use crate::token::cose::encrypted::{EncryptCryptoBackend, AES_GCM_TAG_LEN};
30
use crate::token::cose::header_util::HeaderParam;
31
use crate::token::cose::key::{CoseEc2Key, CoseSymmetricKey, EllipticCurve};
32
use crate::token::cose::maced::MacCryptoBackend;
33
use crate::token::cose::recipient::KeyDistributionCryptoBackend;
34
use crate::token::cose::signed::SignCryptoBackend;
35
use crate::token::cose::{aes_ccm_algorithm_tag_len, CryptoBackend};
36

37
/// Represents an error caused by the OpenSSL cryptographic backend.
38
#[derive(Debug, Display)]
×
39
#[non_exhaustive]
40
pub enum CoseOpensslCipherError {
41
    /// Standard OpenSSL error (represented as an [`ErrorStack`] in the openssl library crate).
42
    OpensslError(ErrorStack),
×
43
    /// AES key error.
44
    AesKeyError(openssl::aes::KeyError),
×
45
    /// Other error (error message is provided as a string).
46
    Other(&'static str),
×
47
}
48

49
impl From<ErrorStack> for CoseOpensslCipherError {
50
    fn from(value: ErrorStack) -> Self {
18✔
51
        CoseOpensslCipherError::OpensslError(value)
18✔
52
    }
18✔
53
}
54

55
impl From<openssl::aes::KeyError> for CoseOpensslCipherError {
56
    fn from(value: openssl::aes::KeyError) -> Self {
1✔
57
        CoseOpensslCipherError::AesKeyError(value)
1✔
58
    }
1✔
59
}
60

61
impl From<ErrorStack> for CoseCipherError<CoseOpensslCipherError> {
62
    fn from(value: ErrorStack) -> Self {
18✔
63
        CoseCipherError::Other(value.into())
18✔
64
    }
18✔
65
}
66

67
impl From<openssl::aes::KeyError> for CoseCipherError<CoseOpensslCipherError> {
68
    fn from(value: openssl::aes::KeyError) -> Self {
1✔
69
        CoseCipherError::Other(value.into())
1✔
70
    }
1✔
71
}
72

73
/// Context for the OpenSSL cryptographic backend.
74
///
75
/// Can be used as a [`CryptoBackend`] for COSE operations.
76
///
77
/// Generic properties of this backend:
78
/// - [ ] Can derive EC public key components if only the private component (d) is present.
79
/// - [ ] Can work with compressed EC public keys (EC keys using point compression)
80
///
81
/// Algorithm support:
82
/// - Signature Algorithms (for COSE_Sign and COSE_Sign1)
83
///     - [x] ECDSA
84
///         - [x] ES256
85
///         - [x] ES384
86
///         - [x] ES512
87
///         - [ ] ES256K
88
///     - [ ] EdDSA
89
/// - Message Authentication Code Algorithms (for COSE_Mac and COSE_Mac0)
90
///     - [x] HMAC
91
///         - [ ] HMAC 256/64
92
///         - [x] HMAC 256/256
93
///         - [x] HMAC 384/384
94
///         - [x] HMAC 512/512
95
///     - [ ] AES-CBC-MAC
96
///         - [ ] AES-MAC 128/64
97
///         - [ ] AES-MAC 256/64
98
///         - [ ] AES-MAC 128/128
99
///         - [ ] AES-MAC 256/128
100
/// - Content Encryption Algorithms (for COSE_Encrypt and COSE_Encrypt0)
101
///     - [x] AES-GCM
102
///         - [x] A128GCM
103
///         - [x] A192GCM
104
///         - [x] A256GCM
105
///     - [x] AES-CCM
106
///         - [x] AES-CCM-16-64-128
107
///         - [x] AES-CCM-16-64-256
108
///         - [x] AES-CCM-64-64-128
109
///         - [x] AES-CCM-64-64-256
110
///         - [x] AES-CCM-16-128-128
111
///         - [x] AES-CCM-16-128-256
112
///         - [x] AES-CCM-64-128-128
113
///         - [x] AES-CCM-64-128-256
114
///     - [ ] ChaCha20/Poly1305
115
/// - Content Key Distribution Methods (for COSE_Recipients)
116
///     - Direct Encryption
117
///         - [ ] Direct Key with KDF
118
///             - [ ] direct+HKDF-SHA-256
119
///             - [ ] direct+HKDF-SHA-512
120
///             - [ ] direct+HKDF-AES-128
121
///             - [ ] direct+HKDF-AES-256
122
///     - Key Wrap
123
///         - [x] AES Key Wrap
124
///             - [x] A128KW
125
///             - [x] A192KW
126
///             - [x] A256KW
127
///     - Direct Key Agreement
128
///         - [ ] Direct ECDH
129
///             - [ ] ECDH-ES + HKDF-256
130
///             - [ ] ECDH-ES + HKDF-512
131
///             - [ ] ECDH-SS + HKDF-256
132
///             - [ ] ECDH-SS + HKDF-512
133
///     - Key Agreement with Key Wrap
134
///         - [ ] ECDH with Key Wrap
135
///             - [ ] ECDH-ES + A128KW
136
///             - [ ] ECDH-ES + A192KW
137
///             - [ ] ECDH-ES + A256KW
138
///             - [ ] ECDH-SS + A128KW
139
///             - [ ] ECDH-SS + A192KW
140
///             - [ ] ECDH-SS + A256KW
141
///
142
/// Elliptic Curve support (for EC algorithms):
143
/// - ES256/ES384/ES512 [^1]
144
///     - [x] P-256
145
///     - [x] P-384
146
///     - [x] P-521
147
/// - ES256K
148
///     - [ ] secp256k1
149
/// - EdDSA
150
///     - [ ] Ed448
151
///     - [ ] Ed25519
152
/// - ECDH
153
///     - [ ] X448
154
///     - [ ] X25519
155
///
156
/// [^1]: RFC 9053, Section 2.1 suggests using ES256 only with curve P-256, ES384 with curve P-384
157
///       and ES512 only with curve P-521.
158
#[derive(Default)]
×
159
pub struct OpensslContext {}
160

161
impl OpensslContext {
162
    /// Creates a new OpenSSL context for use with COSE algorithms.
163
    #[must_use]
164
    pub fn new() -> OpensslContext {
239✔
165
        OpensslContext {}
166
    }
239✔
167
}
168

169
impl CryptoBackend for OpensslContext {
170
    type Error = CoseOpensslCipherError;
171

172
    fn generate_rand(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
74✔
173
        openssl::rand::rand_bytes(buf).map_err(CoseOpensslCipherError::from)
74✔
174
    }
74✔
175
}
176

177
impl SignCryptoBackend for OpensslContext {
178
    fn sign_ecdsa(
35✔
179
        &mut self,
180
        alg: iana::Algorithm,
181
        key: &CoseEc2Key<'_, Self::Error>,
182
        target: &[u8],
183
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
184
        let (pad_size, group) = get_ecdsa_group_params(key)?;
35!
185
        let hash = get_algorithm_hash_function(alg)?;
35!
186

187
        // Possible truncation is fine, the key size will never exceed the size of an i32.
188
        #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
189
        sign_ecdsa(&group, pad_size as i32, hash, key, target)
35✔
190
    }
35✔
191

192
    fn verify_ecdsa(
54✔
193
        &mut self,
194
        alg: iana::Algorithm,
195
        key: &CoseEc2Key<'_, Self::Error>,
196
        signature: &[u8],
197
        target: &[u8],
198
    ) -> Result<(), CoseCipherError<Self::Error>> {
199
        let (pad_size, group) = get_ecdsa_group_params(key)?;
54!
200
        let hash = get_algorithm_hash_function(alg)?;
54!
201

202
        verify_ecdsa(&group, pad_size, hash, key, signature, target)
54✔
203
    }
54✔
204
}
205

206
/// Determine the openssl [`EcGroup`] instance and coordinate size that should be used for the given
207
/// ECDSA key (based on its curve).
208
fn get_ecdsa_group_params(
89✔
209
    key: &CoseEc2Key<'_, CoseOpensslCipherError>,
210
) -> Result<(usize, EcGroup), CoseCipherError<CoseOpensslCipherError>> {
211
    match &key.crv {
89!
212
        EllipticCurve::Assigned(iana::EllipticCurve::P_256) => {
213
            // ECDSA using P-256 curve, coordinates are padded to 256 bits
214
            Ok((32, EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap()))
74✔
215
        }
216
        EllipticCurve::Assigned(iana::EllipticCurve::P_384) => {
217
            // ECDSA using P-384 curve, coordinates are padded to 384 bits
218
            Ok((48, EcGroup::from_curve_name(Nid::SECP384R1).unwrap()))
9✔
219
        }
220
        EllipticCurve::Assigned(iana::EllipticCurve::P_521) => {
221
            // ECDSA using P-521 curve, coordinates are padded to 528 bits (521 bits rounded up
222
            // to the nearest full bytes).
223
            Ok((66, EcGroup::from_curve_name(Nid::SECP521R1).unwrap()))
6✔
224
        }
225
        v => Err(CoseCipherError::UnsupportedCurve(v.clone())),
×
226
    }
227
}
89✔
228

229
/// Determine the hash function (represented in OpenSSL as a [`MessageDigest`]) that should be used
230
/// for a given [`iana::Algorithm`].
231
fn get_algorithm_hash_function(
167✔
232
    alg: iana::Algorithm,
233
) -> Result<MessageDigest, CoseCipherError<CoseOpensslCipherError>> {
234
    match alg {
167!
235
        iana::Algorithm::ES256 | iana::Algorithm::HMAC_256_256 => Ok(MessageDigest::sha256()),
135✔
236
        iana::Algorithm::ES384 | iana::Algorithm::HMAC_384_384 => Ok(MessageDigest::sha384()),
14✔
237
        iana::Algorithm::ES512 | iana::Algorithm::HMAC_512_512 => Ok(MessageDigest::sha512()),
18✔
238
        v => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned(
×
239
            v,
240
        ))),
×
241
    }
242
}
167✔
243

244
/// Perform an ECDSA signature operation with the given parameters.
245
fn sign_ecdsa(
35✔
246
    group: &EcGroup,
247
    pad_size: i32,
248
    hash: MessageDigest,
249
    key: &CoseEc2Key<'_, CoseOpensslCipherError>,
250
    target: &[u8],
251
) -> Result<Vec<u8>, CoseCipherError<CoseOpensslCipherError>> {
252
    let private_key = cose_ec2_to_ec_private_key(key, group).map_err(CoseCipherError::from)?;
35!
253

254
    let mut signer = Signer::new(
35✔
255
        hash,
256
        &*PKey::from_ec_key(private_key).map_err(CoseOpensslCipherError::from)?,
35!
257
    )
258
    .map_err(CoseOpensslCipherError::from)?;
35✔
259

260
    // generated signature is of DER format, need to convert it to COSE key format
261
    let der_signature = signer
35!
262
        .sign_oneshot_to_vec(target)
263
        .map_err(CoseOpensslCipherError::from)
264
        .map_err(CoseCipherError::from)?;
×
265

266
    let ecdsa_sig = EcdsaSig::from_der(der_signature.as_slice())
35!
267
        .map_err(CoseOpensslCipherError::from)
268
        .map_err(CoseCipherError::from)?;
×
269

270
    // See RFC 8152, section 8.1
271
    let mut sig = ecdsa_sig
35!
272
        .r()
273
        .to_vec_padded(pad_size)
274
        .map_err(CoseOpensslCipherError::from)
275
        .map_err(CoseCipherError::from)?;
×
276
    let mut s_vec = ecdsa_sig
35!
277
        .s()
278
        .to_vec_padded(pad_size)
279
        .map_err(CoseOpensslCipherError::from)
280
        .map_err(CoseCipherError::from)?;
×
281
    sig.append(&mut s_vec);
35✔
282

283
    Ok(sig)
35✔
284
}
35✔
285

286
/// Perform an ECDSA verification operation with the given parameters.
287
fn verify_ecdsa(
54✔
288
    group: &EcGroup,
289
    pad_size: usize,
290
    hash: MessageDigest,
291
    key: &CoseEc2Key<'_, CoseOpensslCipherError>,
292
    signature: &[u8],
293
    signed_data: &[u8],
294
) -> Result<(), CoseCipherError<CoseOpensslCipherError>> {
295
    let public_key = cose_ec2_to_ec_public_key(key, group).map_err(CoseCipherError::from)?;
54!
296
    let pkey = PKey::from_ec_key(public_key).map_err(CoseOpensslCipherError::from)?;
54!
297

298
    let mut verifier = Verifier::new(hash, &pkey).map_err(CoseOpensslCipherError::from)?;
54!
299

300
    // signature is in COSE format, need to convert to DER format.
301
    let r = BigNum::from_slice(&signature[..pad_size]).map_err(CoseOpensslCipherError::from)?;
54!
302
    let s = BigNum::from_slice(&signature[pad_size..]).map_err(CoseOpensslCipherError::from)?;
54!
303
    let signature =
304
        EcdsaSig::from_private_components(r, s).map_err(CoseOpensslCipherError::from)?;
54!
305
    // Note: EcdsaSig has its own "verify" method, but it is deprecated since OpenSSL
306
    // 3.0, which is why it's not used here.
307
    let der_signature = signature.to_der().map_err(CoseOpensslCipherError::from)?;
54!
308

309
    verifier
54✔
310
        .verify_oneshot(der_signature.as_slice(), signed_data)
54✔
311
        .map_err(CoseOpensslCipherError::from)
312
        .map_err(CoseCipherError::from)
313
        .and_then(|verification_successful| {
54✔
314
            verification_successful
108✔
315
                .then_some(())
316
                .ok_or(CoseCipherError::VerificationFailure)
54✔
317
        })
54✔
318
}
54!
319

320
/// Converts a private [`CoseEc2Key`] instance to its corresponding representation as an [`EcKey`]
321
/// in `openssl`.
322
fn cose_ec2_to_ec_private_key(
35✔
323
    key: &CoseEc2Key<'_, CoseOpensslCipherError>,
324
    group: &EcGroup,
325
) -> Result<EcKey<Private>, CoseCipherError<CoseOpensslCipherError>> {
326
    let public_key = cose_ec2_to_ec_public_key(key, group)?;
35!
327

328
    EcKey::<Private>::from_private_components(
35✔
329
        group,
35✔
330
        &*BigNum::from_slice(
35✔
331
            // According to the contract of the trait, this should be ensured by the caller, so it's
332
            // fine to panic here.
333
            key.d
35!
334
                .expect("key provided to backend has no private component"),
335
        )
336
        .map_err(CoseCipherError::<CoseOpensslCipherError>::from)?,
×
337
        public_key.public_key(),
35✔
338
    )
339
    .map_err(CoseCipherError::<CoseOpensslCipherError>::from)
340
}
35✔
341

342
/// Converts a public [`CoseEc2Key`] instance to its corresponding representation as an [`EcKey`]
343
/// in `openssl`.
344
fn cose_ec2_to_ec_public_key(
89✔
345
    key: &CoseEc2Key<'_, CoseOpensslCipherError>,
346
    group: &EcGroup,
347
) -> Result<EcKey<Public>, CoseCipherError<CoseOpensslCipherError>> {
348
    // TODO X and Y can be recomputed and are not strictly required if D is known
349
    //      (RFC 8152, Section 13.1.1)
350
    if key.x.is_none() || key.y.is_none() {
89!
351
        return Err(CoseCipherError::UnsupportedKeyDerivation);
×
352
    }
353

354
    EcKey::<Public>::from_public_key_affine_coordinates(
89✔
355
        group,
89✔
356
        &*BigNum::from_slice(key.x.unwrap())
89!
357
            .map_err(CoseCipherError::<CoseOpensslCipherError>::from)?,
×
358
        &*BigNum::from_slice(key.y.unwrap())
89!
359
            .map_err(CoseCipherError::<CoseOpensslCipherError>::from)?,
×
360
    )
361
    .map_err(CoseCipherError::<CoseOpensslCipherError>::from)
362
}
89✔
363

364
impl EncryptCryptoBackend for OpensslContext {
365
    fn encrypt_aes_gcm(
40✔
366
        &mut self,
367
        algorithm: iana::Algorithm,
368
        key: CoseSymmetricKey<'_, Self::Error>,
369
        plaintext: &[u8],
370
        aad: &[u8],
371
        iv: &[u8],
372
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
373
        let cipher = algorithm_to_cipher(algorithm)?;
40✔
374
        let mut ctx = CipherCtx::new()?;
40!
375
        // So, apparently OpenSSL requires a very specific order of operations which differs
376
        // slightly for AES-GCM and AES-CCM in order to work.
377
        // It would have just been too easy if you could just generalize and reuse the code for
378
        // AES-CCM and AES-GCM, right?
379

380
        // Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode
381
        // for reference.
382
        // 1. First, we set the cipher.
383
        ctx.encrypt_init(Some(cipher), None, None)?;
40!
384
        // 2. For GCM, we set the IV length _before_ setting key and IV.
385
        //    We do not set the tag length, as it is fixed for AES-GCM.
386
        ctx.set_iv_length(iv.len())?;
40!
387
        // 3. Now we can set key and IV.
388
        ctx.encrypt_init(None, Some(key.k), Some(iv))?;
40!
389
        let mut ciphertext = vec![];
40✔
390
        // Unlike for CCM, we *must not* set the data length here, otherwise encryption *will fail*.
391
        // 4. Then, we *must* set the AAD _before_ setting the plaintext.
392
        ctx.cipher_update(aad, None)?;
40!
393
        // 5. Finally, we must provide all plaintext in a single call.
394
        ctx.cipher_update_vec(plaintext, &mut ciphertext)?;
40!
395
        // 6. Then, we can finish the operation.
396
        ctx.cipher_final_vec(&mut ciphertext)?;
40!
397
        let ciphertext_len = ciphertext.len();
40✔
398
        ciphertext.resize(ciphertext_len + AES_GCM_TAG_LEN, 0u8);
40✔
399
        ctx.tag(&mut ciphertext[ciphertext_len..])?;
80!
400
        Ok(ciphertext)
40✔
401
    }
40✔
402

403
    fn decrypt_aes_gcm(
67✔
404
        &mut self,
405
        algorithm: iana::Algorithm,
406
        key: CoseSymmetricKey<'_, Self::Error>,
407
        ciphertext_with_tag: &[u8],
408
        aad: &[u8],
409
        iv: &[u8],
410
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
411
        let cipher = algorithm_to_cipher(algorithm)?;
67✔
412
        let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - AES_GCM_TAG_LEN)..];
67✔
413
        let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - AES_GCM_TAG_LEN)];
67✔
414

415
        let mut ctx = CipherCtx::new()?;
67!
416
        // Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Decryption_using_GCM_mode
417
        // for reference.
418
        // 1. First, we set the cipher.
419
        ctx.decrypt_init(Some(cipher), None, None)?;
67!
420
        // 2. For GCM, we set the IV length _before_ setting key and IV.
421
        //    We do not set the tag length, as it is fixed for AES-GCM.
422
        ctx.set_iv_length(iv.len())?;
67!
423
        // 3. Now we can set key and IV.
424
        ctx.decrypt_init(None, Some(key.k), Some(iv))?;
67!
425
        // Unlike for CCM, we *must not* set the data length here, otherwise decryption *will fail*.
426
        // 4. Then, we *must* set the AAD _before_ setting the ciphertext.
427
        ctx.cipher_update(aad, None)?;
67!
428
        // 5. After that, we provide the ciphertext in a single call for decryption.
429
        let mut plaintext = vec![0; ciphertext.len()];
67✔
430
        let mut plaintext_size = ctx.cipher_update(ciphertext, Some(&mut plaintext))?;
67!
431
        // 6. For GCM, we must set the tag value right before the finalization call.
432
        ctx.set_tag(auth_tag)?;
134!
433
        // 7. Now we can finalize decryption.
434
        plaintext_size += ctx.cipher_final_vec(&mut plaintext)?;
67✔
435

436
        plaintext.truncate(plaintext_size);
49✔
437

438
        Ok(plaintext)
49✔
439
    }
67✔
440

441
    fn encrypt_aes_ccm(
17✔
442
        &mut self,
443
        algorithm: iana::Algorithm,
444
        key: CoseSymmetricKey<'_, Self::Error>,
445
        plaintext: &[u8],
446
        aad: &[u8],
447
        iv: &[u8],
448
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
449
        let cipher = algorithm_to_cipher(algorithm)?;
17✔
450
        let tag_len = aes_ccm_algorithm_tag_len(algorithm)?;
17!
451
        let mut ctx = CipherCtx::new()?;
17!
452
        // Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_CCM_mode
453
        // for reference.
454
        // 1. First, we set the cipher.
455
        ctx.encrypt_init(Some(cipher), None, None)?;
17!
456
        // 2. At least for CCM, we *must* set the tag and IV length _before_ setting key and IV.
457
        //    (https://github.com/sfackler/rust-openssl/pull/1594#issue-1105067105)
458
        ctx.set_iv_length(iv.len())?;
17!
459
        ctx.set_tag_length(tag_len)?;
17!
460
        // 3. Now we can set key and IV.
461
        ctx.encrypt_init(None, Some(key.k), Some(iv))?;
17!
462
        let mut ciphertext = vec![];
17✔
463
        // 4. For CCM, we *must* then inform OpenSSL about the size of the plaintext data _before_
464
        //    setting the AAD.
465
        ctx.set_data_len(plaintext.len())?;
17!
466
        // 5. Then, we *must* set the AAD _before_ setting the plaintext.
467
        ctx.cipher_update(aad, None)?;
17!
468
        // 6. Finally, we must provide all plaintext in a single call.
469
        ctx.cipher_update_vec(plaintext, &mut ciphertext)?;
17!
470
        // 7. Then, we can finish the operation.
471
        ctx.cipher_final_vec(&mut ciphertext)?;
17!
472
        let ciphertext_len = ciphertext.len();
17✔
473
        ciphertext.resize(ciphertext_len + tag_len, 0u8);
17✔
474
        ctx.tag(&mut ciphertext[ciphertext_len..])?;
34!
475
        Ok(ciphertext)
17✔
476
    }
17✔
477

478
    fn decrypt_aes_ccm(
33✔
479
        &mut self,
480
        algorithm: iana::Algorithm,
481
        key: CoseSymmetricKey<'_, Self::Error>,
482
        ciphertext_with_tag: &[u8],
483
        aad: &[u8],
484
        iv: &[u8],
485
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
486
        let cipher = algorithm_to_cipher(algorithm)?;
33✔
487
        let tag_len = aes_ccm_algorithm_tag_len(algorithm)?;
33!
488
        let auth_tag = &ciphertext_with_tag[(ciphertext_with_tag.len() - tag_len)..];
33✔
489
        let ciphertext = &ciphertext_with_tag[..(ciphertext_with_tag.len() - tag_len)];
33✔
490

491
        let mut ctx = CipherCtx::new()?;
33!
492
        // Refer to https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Decryption_using_CCM_mode
493
        // for reference.
494
        // 1. First, we set the cipher.
495
        ctx.decrypt_init(Some(cipher), None, None)?;
33!
496
        // 2. At least for CCM, we *must* set the tag and IV length _before_ setting key and IV.
497
        //    (https://github.com/sfackler/rust-openssl/pull/1594#issue-1105067105)
498
        ctx.set_iv_length(iv.len())?;
33!
499
        ctx.set_tag(auth_tag)?;
33!
500
        // 3. Now we can set key and IV.
501
        ctx.decrypt_init(None, Some(key.k), Some(iv))?;
33!
502
        // 4. For CCM, we *must* then inform OpenSSL about the size of the ciphertext data _before_
503
        //    setting the AAD.
504
        ctx.set_data_len(ciphertext.len())?;
66!
505
        // 5. Then, we *must* set the AAD _before_ setting the ciphertext.
506
        ctx.cipher_update(aad, None)?;
33!
507
        // 6. Finally, we must provide all ciphertext in a single call for decryption.
508
        let mut plaintext = vec![0; ciphertext.len()];
33✔
509
        let plaintext_len = ctx.cipher_update(ciphertext, Some(&mut plaintext))?;
33!
510
        plaintext.truncate(plaintext_len);
33✔
511
        // No call to cipher_final() here, I guess?
512
        // The official examples in the OpenSSL wiki don't finalize, so we won't either.
513

514
        Ok(plaintext)
33✔
515
    }
33✔
516
}
517

518
/// Converts the provided [`iana::Algorithm`] to an OpenSSL [`Cipher`] that can be used for a
519
/// symmetric [`CipherCtx`].
520
fn algorithm_to_cipher(
157✔
521
    algorithm: iana::Algorithm,
522
) -> Result<&'static CipherRef, CoseCipherError<CoseOpensslCipherError>> {
523
    match algorithm {
157!
524
        iana::Algorithm::A128GCM => Ok(openssl::cipher::Cipher::aes_128_gcm()),
75✔
525
        iana::Algorithm::A192GCM => Ok(openssl::cipher::Cipher::aes_192_gcm()),
15✔
526
        iana::Algorithm::A256GCM => Ok(openssl::cipher::Cipher::aes_256_gcm()),
17✔
NEW
527
        iana::Algorithm::A128KW => Ok(openssl::cipher::Cipher::aes_128_ecb()),
×
NEW
528
        iana::Algorithm::A192KW => Ok(openssl::cipher::Cipher::aes_192_ecb()),
×
NEW
529
        iana::Algorithm::A256KW => Ok(openssl::cipher::Cipher::aes_256_ecb()),
×
530
        iana::Algorithm::AES_CCM_16_64_128
531
        | iana::Algorithm::AES_CCM_64_64_128
532
        | iana::Algorithm::AES_CCM_16_128_128
533
        | iana::Algorithm::AES_CCM_64_128_128 => Ok(openssl::cipher::Cipher::aes_128_ccm()),
26✔
534
        iana::Algorithm::AES_CCM_16_64_256
535
        | iana::Algorithm::AES_CCM_64_64_256
536
        | iana::Algorithm::AES_CCM_16_128_256
537
        | iana::Algorithm::AES_CCM_64_128_256 => Ok(openssl::cipher::Cipher::aes_256_ccm()),
24✔
UNCOV
538
        v => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned(
×
539
            v,
540
        ))),
×
541
    }
542
}
157✔
543

544
impl KeyDistributionCryptoBackend for OpensslContext {
545
    fn aes_key_wrap(
12✔
546
        &mut self,
547
        _algorithm: iana::Algorithm,
548
        key: CoseSymmetricKey<'_, Self::Error>,
549
        plaintext: &[u8],
550
        iv: &[u8],
551
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
552
        let key = AesKey::new_encrypt(key.k)?;
12✔
553
        let iv: [u8; 8] = iv.try_into().map_err(|_e| {
12!
554
            CoseCipherError::InvalidHeaderParam(
×
555
                HeaderParam::Generic(iana::HeaderParameter::Iv),
×
556
                Value::Bytes(iv.to_vec()),
×
557
            )
558
        })?;
×
559
        let mut output = vec![0u8; plaintext.len() + 8];
12✔
560
        let output_len = wrap_key(&key, Some(iv), output.as_mut_slice(), plaintext)?;
24!
561
        output.truncate(output_len);
12✔
562
        Ok(output)
12✔
563
    }
12✔
564

565
    fn aes_key_unwrap(
20✔
566
        &mut self,
567
        _algorithm: iana::Algorithm,
568
        key: CoseSymmetricKey<'_, Self::Error>,
569
        ciphertext: &[u8],
570
        iv: &[u8],
571
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
572
        let key = AesKey::new_decrypt(key.k)?;
20✔
573
        let iv: [u8; 8] = iv.try_into().map_err(|_e| {
20!
574
            CoseCipherError::InvalidHeaderParam(
×
575
                HeaderParam::Generic(iana::HeaderParameter::Iv),
×
576
                Value::Bytes(iv.to_vec()),
×
577
            )
578
        })?;
×
579
        let mut output = vec![0u8; ciphertext.len() - 8];
20✔
580
        let output_len = unwrap_key(&key, Some(iv), output.as_mut_slice(), ciphertext)?;
40!
581
        output.truncate(output_len);
19✔
582
        Ok(output)
19✔
583
    }
20✔
584
}
585

586
/// Computes an HMAC for `input` using the given `algorithm` and `key`.
587
fn compute_hmac(
78✔
588
    algorithm: iana::Algorithm,
589
    key: &CoseSymmetricKey<'_, CoseOpensslCipherError>,
590
    input: &[u8],
591
) -> Result<Vec<u8>, CoseCipherError<CoseOpensslCipherError>> {
592
    let hash = get_algorithm_hash_function(algorithm)?;
78!
593
    let hmac_key = PKey::hmac(key.k)?;
78!
594
    let mut signer = Signer::new(hash, &hmac_key)?;
78!
595
    signer
78✔
596
        .sign_oneshot_to_vec(input)
597
        .map_err(CoseCipherError::from)
598
}
78✔
599

600
impl MacCryptoBackend for OpensslContext {
601
    fn compute_hmac(
30✔
602
        &mut self,
603
        algorithm: iana::Algorithm,
604
        key: CoseSymmetricKey<'_, Self::Error>,
605
        data: &[u8],
606
    ) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
607
        compute_hmac(algorithm, &key, data)
30✔
608
    }
30✔
609

610
    fn verify_hmac(
48✔
611
        &mut self,
612
        algorithm: iana::Algorithm,
613
        key: CoseSymmetricKey<'_, Self::Error>,
614
        tag: &[u8],
615
        data: &[u8],
616
    ) -> Result<(), CoseCipherError<Self::Error>> {
617
        let hmac = compute_hmac(algorithm, &key, data)?;
48!
618
        // Use openssl::memcmp::eq to prevent timing attacks.
619
        if openssl::memcmp::eq(hmac.as_slice(), tag) {
48✔
620
            Ok(())
30✔
621
        } else {
622
            Err(CoseCipherError::VerificationFailure)
18✔
623
        }
624
    }
48✔
625
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc