opcua_crypto/
pkey.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2022 Adam Lock
4
5//! Asymmetric encryption / decryption, signing / verification wrapper.
6use std::{
7    self,
8    fmt::{self, Debug, Formatter},
9    result::Result,
10};
11
12use openssl::{hash, pkey, rsa, sign};
13
14use opcua_types::status_code::StatusCode;
15use openssl::sign::RsaPssSaltlen;
16
17#[derive(Copy, Clone, Debug, PartialEq)]
18pub enum RsaPadding {
19    Pkcs1,
20    OaepSha1,
21    OaepSha256,
22    Pkcs1Pss,
23}
24
25impl Into<rsa::Padding> for RsaPadding {
26    fn into(self) -> rsa::Padding {
27        match self {
28            RsaPadding::Pkcs1 => rsa::Padding::PKCS1,
29            RsaPadding::OaepSha1 => rsa::Padding::PKCS1_OAEP,
30            RsaPadding::Pkcs1Pss => rsa::Padding::PKCS1_PSS,
31            // Note: This is the right padding but not the right hash and must be handled by special case in the code
32            RsaPadding::OaepSha256 => rsa::Padding::PKCS1_OAEP,
33        }
34    }
35}
36
37#[derive(Debug)]
38pub struct PKeyError;
39
40impl fmt::Display for PKeyError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "PKeyError")
43    }
44}
45
46impl std::error::Error for PKeyError {}
47
48/// This is a wrapper around an `OpenSSL` asymmetric key pair. Since openssl 0.10, the PKey is either
49/// a public or private key so we have to differentiate that as well.
50pub struct PKey<T> {
51    pub(crate) value: pkey::PKey<T>,
52}
53
54/// A public key
55pub type PublicKey = PKey<pkey::Public>;
56// A private key
57pub type PrivateKey = PKey<pkey::Private>;
58
59impl<T> Debug for PKey<T> {
60    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
61        // This impl will not write out the key, but it exists to keep structs happy
62        // that contain a key as a field
63        write!(f, "[pkey]")
64    }
65}
66
67pub trait KeySize {
68    fn bit_length(&self) -> usize;
69
70    fn size(&self) -> usize {
71        self.bit_length() / 8
72    }
73
74    fn calculate_cipher_text_size(&self, data_size: usize, padding: RsaPadding) -> usize {
75        let plain_text_block_size = self.plain_text_block_size(padding);
76        let block_count = if data_size % plain_text_block_size == 0 {
77            data_size / plain_text_block_size
78        } else {
79            (data_size / plain_text_block_size) + 1
80        };
81        block_count * self.cipher_text_block_size()
82    }
83
84    fn plain_text_block_size(&self, padding: RsaPadding) -> usize {
85        // flen must not be more than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based padding modes,
86        // not more than RSA_size(rsa) - 42 for RSA_PKCS1_OAEP_PADDING and exactly RSA_size(rsa)
87        // for RSA_NO_PADDING.
88        match padding {
89            RsaPadding::Pkcs1 => self.size() - 11,
90            RsaPadding::OaepSha1 => self.size() - 42,
91            RsaPadding::OaepSha256 => self.size() - 66,
92            _ => panic!("Unsupported padding"),
93        }
94    }
95
96    fn cipher_text_block_size(&self) -> usize {
97        self.size()
98    }
99}
100
101impl KeySize for PrivateKey {
102    /// Length in bits
103    fn bit_length(&self) -> usize {
104        self.value.bits() as usize
105    }
106}
107
108impl PrivateKey {
109    pub fn new(bit_length: u32) -> PrivateKey {
110        PKey {
111            value: {
112                let rsa = rsa::Rsa::generate(bit_length).unwrap();
113                pkey::PKey::from_rsa(rsa).unwrap()
114            },
115        }
116    }
117
118    pub fn wrap_private_key(pkey: pkey::PKey<pkey::Private>) -> PrivateKey {
119        PrivateKey { value: pkey }
120    }
121
122    pub fn from_pem(pem: &[u8]) -> Result<PrivateKey, PKeyError> {
123        pkey::PKey::private_key_from_pem(pem)
124            .map(|value| PKey { value })
125            .map_err(|_| {
126                error!("Cannot produce a private key from the data supplied");
127                PKeyError
128            })
129    }
130
131    pub fn private_key_to_pem(&self) -> Result<Vec<u8>, PKeyError> {
132        self.value.private_key_to_pem_pkcs8().map_err(|_| {
133            error!("Cannot turn private key to PEM");
134            PKeyError
135        })
136    }
137
138    /// Creates a message digest from the specified block of data and then signs it to return a signature
139    fn sign(
140        &self,
141        message_digest: hash::MessageDigest,
142        data: &[u8],
143        signature: &mut [u8],
144        padding: RsaPadding,
145    ) -> Result<usize, StatusCode> {
146        trace!("RSA signing");
147        if let Ok(mut signer) = sign::Signer::new(message_digest, &self.value) {
148            let _ = signer.set_rsa_padding(padding.into());
149            let _ = signer.set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH);
150            if signer.update(data).is_ok() {
151                return signer
152                    .sign_to_vec()
153                    .map(|result| {
154                        trace!(
155                            "Signature result, len {} = {:?}, copying to signature len {}",
156                            result.len(),
157                            result,
158                            signature.len()
159                        );
160                        signature.copy_from_slice(&result);
161                        result.len()
162                    })
163                    .map_err(|err| {
164                        debug!("Cannot sign data - error = {:?}", err);
165                        StatusCode::BadUnexpectedError
166                    });
167            }
168        }
169        Err(StatusCode::BadUnexpectedError)
170    }
171
172    /// Signs the data using RSA-SHA1
173    pub fn sign_sha1(&self, data: &[u8], signature: &mut [u8]) -> Result<usize, StatusCode> {
174        self.sign(
175            hash::MessageDigest::sha1(),
176            data,
177            signature,
178            RsaPadding::Pkcs1,
179        )
180    }
181
182    /// Signs the data using RSA-SHA256
183    pub fn sign_sha256(&self, data: &[u8], signature: &mut [u8]) -> Result<usize, StatusCode> {
184        self.sign(
185            hash::MessageDigest::sha256(),
186            data,
187            signature,
188            RsaPadding::Pkcs1,
189        )
190    }
191
192    /// Signs the data using RSA-SHA256-PSS
193    pub fn sign_sha256_pss(&self, data: &[u8], signature: &mut [u8]) -> Result<usize, StatusCode> {
194        self.sign(
195            hash::MessageDigest::sha256(),
196            data,
197            signature,
198            RsaPadding::Pkcs1Pss,
199        )
200    }
201
202    /// Decrypts data in src to dst using the specified padding and returning the size of the decrypted
203    /// data in bytes or an error.
204    pub fn private_decrypt(
205        &self,
206        src: &[u8],
207        dst: &mut [u8],
208        padding: RsaPadding,
209    ) -> Result<usize, PKeyError> {
210        // decrypt data using our private key
211        let cipher_text_block_size = self.cipher_text_block_size();
212        let rsa = self.value.rsa().unwrap();
213        let is_oaep_sha256 = padding == RsaPadding::OaepSha256;
214        let rsa_padding: rsa::Padding = padding.into();
215
216        // Decrypt the data
217        let mut src_idx = 0;
218        let mut dst_idx = 0;
219
220        let src_len = src.len();
221        while src_idx < src_len {
222            // Decrypt and advance
223            dst_idx += {
224                let src = &src[src_idx..(src_idx + cipher_text_block_size)];
225                let dst = &mut dst[dst_idx..(dst_idx + cipher_text_block_size)];
226
227                if is_oaep_sha256 {
228                    oaep_sha256::decrypt(&rsa, src, dst)
229                } else {
230                    rsa.private_decrypt(src, dst, rsa_padding)
231                }.map_err(|err| {
232                    error!("Decryption failed for key size {}, src idx {}, dst idx {}, padding {:?}, error - {:?}", cipher_text_block_size, src_idx, dst_idx, padding, err);
233                    PKeyError
234                })?
235            };
236            src_idx += cipher_text_block_size;
237        }
238        Ok(dst_idx)
239    }
240}
241
242impl KeySize for PublicKey {
243    /// Length in bits
244    fn bit_length(&self) -> usize {
245        self.value.bits() as usize
246    }
247}
248
249impl PublicKey {
250    pub fn wrap_public_key(pkey: pkey::PKey<pkey::Public>) -> PublicKey {
251        PublicKey { value: pkey }
252    }
253
254    /// Verifies that the signature matches the hash / signing key of the supplied data
255    fn verify(
256        &self,
257        message_digest: hash::MessageDigest,
258        data: &[u8],
259        signature: &[u8],
260        padding: RsaPadding,
261    ) -> Result<bool, StatusCode> {
262        trace!(
263            "RSA verifying, against signature {:?}, len {}",
264            signature,
265            signature.len()
266        );
267        if let Ok(mut verifier) = sign::Verifier::new(message_digest, &self.value) {
268            let _ = verifier.set_rsa_padding(padding.into());
269            let _ = verifier.set_rsa_pss_saltlen(RsaPssSaltlen::DIGEST_LENGTH);
270            if verifier.update(data).is_ok() {
271                return verifier
272                    .verify(signature)
273                    .map(|result| {
274                        trace!("Key verified = {:?}", result);
275                        result
276                    })
277                    .map_err(|err| {
278                        debug!("Cannot verify key - error = {:?}", err);
279                        StatusCode::BadUnexpectedError
280                    });
281            }
282        }
283        Err(StatusCode::BadUnexpectedError)
284    }
285
286    /// Verifies the data using RSA-SHA1
287    pub fn verify_sha1(&self, data: &[u8], signature: &[u8]) -> Result<bool, StatusCode> {
288        self.verify(
289            hash::MessageDigest::sha1(),
290            data,
291            signature,
292            RsaPadding::Pkcs1,
293        )
294    }
295
296    /// Verifies the data using RSA-SHA256
297    pub fn verify_sha256(&self, data: &[u8], signature: &[u8]) -> Result<bool, StatusCode> {
298        self.verify(
299            hash::MessageDigest::sha256(),
300            data,
301            signature,
302            RsaPadding::Pkcs1,
303        )
304    }
305
306    /// Verifies the data using RSA-SHA256-PSS
307    pub fn verify_sha256_pss(&self, data: &[u8], signature: &[u8]) -> Result<bool, StatusCode> {
308        self.verify(
309            hash::MessageDigest::sha256(),
310            data,
311            signature,
312            RsaPadding::Pkcs1Pss,
313        )
314    }
315
316    /// Encrypts data from src to dst using the specified padding and returns the size of encrypted
317    /// data in bytes or an error.
318    pub fn public_encrypt(
319        &self,
320        src: &[u8],
321        dst: &mut [u8],
322        padding: RsaPadding,
323    ) -> Result<usize, PKeyError> {
324        let cipher_text_block_size = self.cipher_text_block_size();
325        let plain_text_block_size = self.plain_text_block_size(padding);
326
327        // For reference:
328        //
329        // https://www.openssl.org/docs/man1.0.2/crypto/RSA_public_encrypt.html
330        let rsa = self.value.rsa().unwrap();
331        let is_oaep_sha256 = padding == RsaPadding::OaepSha256;
332        let padding: rsa::Padding = padding.into();
333
334        // Encrypt the data in chunks no larger than the key size less padding
335        let mut src_idx = 0;
336        let mut dst_idx = 0;
337
338        let src_len = src.len();
339        while src_idx < src_len {
340            let bytes_to_encrypt = if src_len < plain_text_block_size {
341                src_len
342            } else if (src_len - src_idx) < plain_text_block_size {
343                src_len - src_idx
344            } else {
345                plain_text_block_size
346            };
347
348            // Encrypt data, advance dst index by number of bytes after encrypted
349            dst_idx += {
350                let src = &src[src_idx..(src_idx + bytes_to_encrypt)];
351                let dst = &mut dst[dst_idx..(dst_idx + cipher_text_block_size)];
352
353                if is_oaep_sha256 {
354                    oaep_sha256::encrypt(&rsa, src, dst)
355                } else {
356                    rsa.public_encrypt(src, dst, padding)
357                }.map_err(|err| {
358                    error!("Encryption failed for bytes_to_encrypt {}, src len {}, src_idx {}, dst len {}, dst_idx {}, cipher_text_block_size {}, plain_text_block_size {}, error - {:?}",
359                           bytes_to_encrypt, src.len(), src_idx, dst.len(), dst_idx, cipher_text_block_size, plain_text_block_size, err);
360                    PKeyError
361                })?
362            };
363
364            // Src advances by bytes to encrypt
365            src_idx += bytes_to_encrypt;
366        }
367
368        Ok(dst_idx)
369    }
370}
371
372/// This module contains a bunch of nasty stuff to implement OAEP-SHA256 since there are no helpers in OpenSSL to do it
373///
374/// https://stackoverflow.com/questions/17784022/how-to-encrypt-data-using-rsa-with-sha-256-as-hash-function-and-mgf1-as-mask-ge
375mod oaep_sha256 {
376    use std::ptr;
377
378    use foreign_types::ForeignType;
379    use libc::*;
380    use openssl::{
381        error,
382        pkey::{Private, Public},
383        rsa::{self, Rsa},
384    };
385    use openssl_sys::*;
386
387    // This sets up the context for encrypting / decrypting with OAEP + SHA256
388    unsafe fn set_evp_ctrl_oaep_sha256(ctx: *mut EVP_PKEY_CTX) {
389        EVP_PKEY_CTX_set_rsa_padding(ctx, rsa::Padding::PKCS1_OAEP.as_raw());
390        let md = EVP_sha256() as *mut EVP_MD;
391        EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md);
392        // This is a hack because OpenSSL crate doesn't expose this const or a wrapper fn
393        const EVP_PKEY_CTRL_RSA_OAEP_MD: c_int = EVP_PKEY_ALG_CTRL + 9;
394        EVP_PKEY_CTX_ctrl(
395            ctx,
396            EVP_PKEY_RSA,
397            EVP_PKEY_OP_TYPE_CRYPT,
398            EVP_PKEY_CTRL_RSA_OAEP_MD,
399            0,
400            md as *mut c_void,
401        );
402    }
403
404    /// Special case implementation uses OAEP with SHA256
405    pub fn decrypt(
406        pkey: &Rsa<Private>,
407        from: &[u8],
408        to: &mut [u8],
409    ) -> Result<usize, error::ErrorStack> {
410        let result;
411        unsafe {
412            let priv_key = EVP_PKEY_new();
413            if !priv_key.is_null() {
414                EVP_PKEY_set1_RSA(priv_key, pkey.as_ptr());
415                let ctx = EVP_PKEY_CTX_new(priv_key, ptr::null_mut());
416                EVP_PKEY_free(priv_key);
417
418                if !ctx.is_null() {
419                    let _ret = EVP_PKEY_decrypt_init(ctx);
420                    set_evp_ctrl_oaep_sha256(ctx);
421
422                    let mut out_len: size_t = to.len();
423                    let ret = EVP_PKEY_decrypt(
424                        ctx,
425                        to.as_mut_ptr(),
426                        &mut out_len,
427                        from.as_ptr(),
428                        from.len(),
429                    );
430                    if ret > 0 && out_len > 0 {
431                        result = Ok(out_len as usize);
432                    } else {
433                        trace!(
434                            "oaep_sha256::decrypt EVP_PKEY_decrypt, ret = {}, out_len = {}",
435                            ret,
436                            out_len
437                        );
438                        result = Err(error::ErrorStack::get());
439                    }
440                    EVP_PKEY_CTX_free(ctx);
441                } else {
442                    trace!("oaep_sha256::decrypt EVP_PKEY_CTX_new");
443                    result = Err(error::ErrorStack::get());
444                }
445            } else {
446                trace!(
447                    "oaep_sha256::decrypt EVP_PKEY_new failed, err {}",
448                    ERR_get_error()
449                );
450                result = Err(error::ErrorStack::get());
451            }
452        }
453
454        result
455    }
456
457    /// Special case implementation uses OAEP with SHA256
458    pub fn encrypt(
459        pkey: &Rsa<Public>,
460        from: &[u8],
461        to: &mut [u8],
462    ) -> Result<usize, error::ErrorStack> {
463        let result;
464        unsafe {
465            let pub_key = EVP_PKEY_new();
466            if !pub_key.is_null() {
467                EVP_PKEY_set1_RSA(pub_key, pkey.as_ptr());
468                let ctx = EVP_PKEY_CTX_new(pub_key, ptr::null_mut());
469                EVP_PKEY_free(pub_key);
470
471                if !ctx.is_null() {
472                    let _ret = EVP_PKEY_encrypt_init(ctx);
473                    set_evp_ctrl_oaep_sha256(ctx);
474
475                    let mut out_len: size_t = to.len();
476                    let ret = EVP_PKEY_encrypt(
477                        ctx,
478                        to.as_mut_ptr(),
479                        &mut out_len,
480                        from.as_ptr(),
481                        from.len(),
482                    );
483                    if ret > 0 && out_len > 0 {
484                        result = Ok(out_len as usize);
485                    } else {
486                        trace!(
487                            "oaep_sha256::encrypt EVP_PKEY_encrypt, ret = {}, out_len = {}",
488                            ret,
489                            out_len
490                        );
491                        result = Err(error::ErrorStack::get());
492                    }
493                    EVP_PKEY_CTX_free(ctx);
494                } else {
495                    trace!("oaep_sha256::encrypt EVP_PKEY_CTX_new");
496                    result = Err(error::ErrorStack::get());
497                }
498            } else {
499                trace!(
500                    "oaep_sha256::encrypt EVP_PKEY_new failed, err {}",
501                    ERR_get_error()
502                );
503                result = Err(error::ErrorStack::get());
504            }
505        }
506        result
507    }
508}