nss/
ec.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::{
6    error::*,
7    pk11::{
8        self,
9        context::HashAlgorithm,
10        slot,
11        types::{Pkcs11Object, PrivateKey as PK11PrivateKey, PublicKey as PK11PublicKey},
12    },
13    util::{assert_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
14};
15use serde_derive::{Deserialize, Serialize};
16use std::{
17    mem,
18    ops::Deref,
19    os::raw::{c_uchar, c_uint, c_void},
20    ptr,
21};
22
23#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
24#[repr(u8)]
25pub enum Curve {
26    P256,
27    P384,
28}
29
30impl Curve {
31    pub fn get_field_len(&self) -> u32 {
32        match &self {
33            Curve::P256 => 32,
34            Curve::P384 => 48,
35        }
36    }
37}
38
39const CRV_P256: &str = "P-256";
40const CRV_P384: &str = "P-384";
41
42#[derive(Serialize, Deserialize, Clone, Debug)]
43pub struct EcKey {
44    curve: String,
45    // The `d` value of the EC Key.
46    private_key: Vec<u8>,
47    // The uncompressed x,y-representation of the public component of the EC Key.
48    public_key: Vec<u8>,
49}
50
51impl EcKey {
52    pub fn new(curve: Curve, private_key: &[u8], public_key: &[u8]) -> Self {
53        let curve = match curve {
54            Curve::P256 => CRV_P256,
55            Curve::P384 => CRV_P384,
56        };
57        Self {
58            curve: curve.to_owned(),
59            private_key: private_key.to_vec(),
60            public_key: public_key.to_vec(),
61        }
62    }
63
64    pub fn from_coordinates(curve: Curve, d: &[u8], x: &[u8], y: &[u8]) -> Result<Self> {
65        let ec_point = create_ec_point_for_coordinates(x, y)?;
66        Ok(EcKey::new(curve, d, &ec_point))
67    }
68
69    pub fn curve(&self) -> Curve {
70        if self.curve == CRV_P256 {
71            return Curve::P256;
72        } else if self.curve == CRV_P384 {
73            return Curve::P384;
74        }
75        unimplemented!("It is impossible to create a curve object with a different CRV.")
76    }
77
78    pub fn public_key(&self) -> &[u8] {
79        &self.public_key
80    }
81
82    pub fn private_key(&self) -> &[u8] {
83        &self.private_key
84    }
85}
86
87fn create_ec_point_for_coordinates(x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
88    if x.len() != y.len() {
89        return Err(ErrorKind::InternalError.into());
90    }
91    let mut buf = vec![0u8; x.len() + y.len() + 1];
92    buf[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
93    let mut offset = 1;
94    buf[offset..offset + x.len()].copy_from_slice(x);
95    offset += x.len();
96    buf[offset..offset + y.len()].copy_from_slice(y);
97    Ok(buf)
98}
99
100pub fn generate_keypair(curve: Curve) -> Result<(PrivateKey, PublicKey)> {
101    assert_nss_initialized();
102    // 1. Create EC params
103    let params_buf = create_ec_params_for_curve(curve)?;
104    let mut params = nss_sys::SECItem {
105        type_: nss_sys::SECItemType::siBuffer as u32,
106        data: params_buf.as_ptr() as *mut c_uchar,
107        len: c_uint::try_from(params_buf.len())?,
108    };
109
110    // 2. Generate the key pair
111    // The following code is adapted from:
112    // https://searchfox.org/mozilla-central/rev/f46e2bf881d522a440b30cbf5cf8d76fc212eaf4/dom/crypto/WebCryptoTask.cpp#2389
113    let mech = nss_sys::CKM_EC_KEY_PAIR_GEN;
114    let slot = slot::get_internal_slot()?;
115    let mut pub_key: *mut nss_sys::SECKEYPublicKey = ptr::null_mut();
116    let prv_key = PrivateKey::from(curve, unsafe {
117        PK11PrivateKey::from_ptr(nss_sys::PK11_GenerateKeyPair(
118            slot.as_mut_ptr(),
119            mech.into(),
120            &mut params as *mut _ as *mut c_void,
121            &mut pub_key,
122            nss_sys::PR_FALSE,
123            nss_sys::PR_FALSE,
124            ptr::null_mut(),
125        ))?
126    });
127    let pub_key = PublicKey::from(curve, unsafe { PK11PublicKey::from_ptr(pub_key)? });
128    Ok((prv_key, pub_key))
129}
130
131pub struct PrivateKey {
132    curve: Curve,
133    wrapped: PK11PrivateKey,
134}
135
136impl Deref for PrivateKey {
137    type Target = PK11PrivateKey;
138    #[inline]
139    fn deref(&self) -> &PK11PrivateKey {
140        &self.wrapped
141    }
142}
143
144impl PrivateKey {
145    pub fn convert_to_public_key(&self) -> Result<PublicKey> {
146        let mut pub_key = self.wrapped.convert_to_public_key()?;
147
148        // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1562046.
149        let field_len = self.curve.get_field_len();
150        let expected_len = 2 * field_len + 1;
151        let mut pub_value = unsafe { (*pub_key.as_ptr()).u.ec.publicValue };
152        if pub_value.len == expected_len - 2 {
153            let old_pub_value_raw = unsafe { sec_item_as_slice(&mut pub_value)?.to_vec() };
154            let mut new_pub_value_raw = vec![0u8; usize::try_from(expected_len)?];
155            new_pub_value_raw[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
156            new_pub_value_raw[1] = u8::try_from(old_pub_value_raw.len())?;
157            new_pub_value_raw[2..].copy_from_slice(&old_pub_value_raw);
158            pub_key = PublicKey::from_bytes(self.curve, &new_pub_value_raw)?.wrapped;
159        }
160        Ok(PublicKey {
161            wrapped: pub_key,
162            curve: self.curve,
163        })
164    }
165
166    #[inline]
167    pub(crate) fn from(curve: Curve, key: PK11PrivateKey) -> Self {
168        Self {
169            curve,
170            wrapped: key,
171        }
172    }
173
174    pub fn curve(&self) -> Curve {
175        self.curve
176    }
177
178    pub fn private_value(&self) -> Result<Vec<u8>> {
179        let mut private_value = self.read_raw_attribute(nss_sys::CKA_VALUE.into()).unwrap();
180        let private_key = unsafe { sec_item_as_slice(private_value.as_mut_ref())?.to_vec() };
181        Ok(private_key)
182    }
183
184    fn from_nss_params(
185        curve: Curve,
186        ec_params: &[u8],
187        ec_point: &[u8],
188        private_value: &[u8],
189    ) -> Result<Self> {
190        // The following code is adapted from:
191        // https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/CryptoKey.cpp#322
192        // These explicit variable type declarations are *VERY* important, as we pass to NSS a pointer to them
193        // and we need these variables to be of the right size!
194        let mut private_key_value: nss_sys::CK_OBJECT_CLASS = nss_sys::CKO_PRIVATE_KEY.into();
195        let mut false_value: nss_sys::CK_BBOOL = nss_sys::CK_FALSE;
196        let mut ec_value: nss_sys::CK_KEY_TYPE = nss_sys::CKK_EC.into();
197        let bbool_size = mem::size_of::<nss_sys::CK_BBOOL>();
198        let key_template = vec![
199            ck_attribute(
200                nss_sys::CKA_CLASS.into(),
201                &mut private_key_value as *mut _ as *mut c_void,
202                mem::size_of::<nss_sys::CK_OBJECT_CLASS>(),
203            )?,
204            ck_attribute(
205                nss_sys::CKA_KEY_TYPE.into(),
206                &mut ec_value as *mut _ as *mut c_void,
207                mem::size_of::<nss_sys::CK_KEY_TYPE>(),
208            )?,
209            ck_attribute(
210                nss_sys::CKA_TOKEN.into(),
211                &mut false_value as *mut _ as *mut c_void,
212                bbool_size,
213            )?,
214            ck_attribute(
215                nss_sys::CKA_SENSITIVE.into(),
216                &mut false_value as *mut _ as *mut c_void,
217                bbool_size,
218            )?,
219            ck_attribute(
220                nss_sys::CKA_PRIVATE.into(),
221                &mut false_value as *mut _ as *mut c_void,
222                bbool_size,
223            )?,
224            // PrivateKeyFromPrivateKeyTemplate sets the ID.
225            ck_attribute(nss_sys::CKA_ID.into(), ptr::null_mut(), 0)?,
226            ck_attribute(
227                nss_sys::CKA_EC_PARAMS.into(),
228                ec_params.as_ptr() as *mut c_void,
229                ec_params.len(),
230            )?,
231            ck_attribute(
232                nss_sys::CKA_EC_POINT.into(),
233                ec_point.as_ptr() as *mut c_void,
234                ec_point.len(),
235            )?,
236            ck_attribute(
237                nss_sys::CKA_VALUE.into(),
238                private_value.as_ptr() as *mut c_void,
239                private_value.len(),
240            )?,
241        ];
242        Ok(Self::from(
243            curve,
244            PK11PrivateKey::from_private_key_template(key_template)?,
245        ))
246    }
247
248    pub fn import(ec_key: &EcKey) -> Result<Self> {
249        // The following code is adapted from:
250        // https://searchfox.org/mozilla-central/rev/66086345467c69685434dd1c5177b30a7511b1a5/dom/crypto/CryptoKey.cpp#652
251        assert_nss_initialized();
252        let curve = ec_key.curve();
253        let ec_params = create_ec_params_for_curve(curve)?;
254        Self::from_nss_params(curve, &ec_params, &ec_key.public_key, &ec_key.private_key)
255    }
256
257    pub fn export(&self) -> Result<EcKey> {
258        let public_key = self.convert_to_public_key()?;
259        let public_key_bytes = public_key.to_bytes()?;
260        let private_key_bytes = self.private_value()?;
261        Ok(EcKey::new(
262            self.curve,
263            &private_key_bytes,
264            &public_key_bytes,
265        ))
266    }
267}
268
269#[inline]
270fn ck_attribute(
271    r#type: nss_sys::CK_ATTRIBUTE_TYPE,
272    p_value: nss_sys::CK_VOID_PTR,
273    value_len: usize,
274) -> Result<nss_sys::CK_ATTRIBUTE> {
275    Ok(nss_sys::CK_ATTRIBUTE {
276        type_: r#type,
277        pValue: p_value,
278        ulValueLen: nss_sys::CK_ULONG::try_from(value_len)?,
279    })
280}
281
282pub struct PublicKey {
283    curve: Curve,
284    wrapped: PK11PublicKey,
285}
286
287impl Deref for PublicKey {
288    type Target = PK11PublicKey;
289    #[inline]
290    fn deref(&self) -> &PK11PublicKey {
291        &self.wrapped
292    }
293}
294
295impl PublicKey {
296    #[inline]
297    pub(crate) fn from(curve: Curve, key: PK11PublicKey) -> Self {
298        Self {
299            curve,
300            wrapped: key,
301        }
302    }
303
304    pub fn curve(&self) -> Curve {
305        self.curve
306    }
307
308    /// ECDSA verify operation
309    pub fn verify(
310        &self,
311        message: &[u8],
312        signature: &[u8],
313        hash_algorithm: HashAlgorithm,
314    ) -> Result<()> {
315        // The following code is adapted from:
316        // https://searchfox.org/mozilla-central/rev/b2716c233e9b4398fc5923cbe150e7f83c7c6c5b/dom/crypto/WebCryptoTask.cpp#1144
317        let signature = nss_sys::SECItem {
318            len: u32::try_from(signature.len())?,
319            data: signature.as_ptr() as *mut u8,
320            type_: 0,
321        };
322        let hash = pk11::context::hash_buf(&hash_algorithm, message)?;
323        let hash = nss_sys::SECItem {
324            len: u32::try_from(hash.len())?,
325            data: hash.as_ptr() as *mut u8,
326            type_: 0,
327        };
328        map_nss_secstatus(|| unsafe {
329            nss_sys::PK11_VerifyWithMechanism(
330                self.as_mut_ptr(),
331                nss_sys::PK11_MapSignKeyType((*self.wrapped.as_ptr()).keyType),
332                ptr::null(),
333                &signature,
334                &hash,
335                ptr::null_mut(),
336            )
337        })?;
338        Ok(())
339    }
340
341    pub fn to_bytes(&self) -> Result<Vec<u8>> {
342        // Some public keys we create do not have an associated PCKS#11 slot
343        // therefore we cannot use `read_raw_attribute(CKA_EC_POINT)`
344        // so we read the `publicValue` field directly instead.
345        let mut ec_point = unsafe { (*self.as_ptr()).u.ec.publicValue };
346        let public_key = unsafe { sec_item_as_slice(&mut ec_point)?.to_vec() };
347        check_pub_key_bytes(&public_key, self.curve)?;
348        Ok(public_key)
349    }
350
351    pub fn from_bytes(curve: Curve, bytes: &[u8]) -> Result<PublicKey> {
352        // The following code is adapted from:
353        // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/CryptoKey.cpp#1078
354        check_pub_key_bytes(bytes, curve)?;
355        let key_data = nss_sys::SECItem {
356            type_: nss_sys::SECItemType::siBuffer as u32,
357            data: bytes.as_ptr() as *mut c_uchar,
358            len: c_uint::try_from(bytes.len())?,
359        };
360        let params_buf = create_ec_params_for_curve(curve)?;
361        let params = nss_sys::SECItem {
362            type_: nss_sys::SECItemType::siBuffer as u32,
363            data: params_buf.as_ptr() as *mut c_uchar,
364            len: c_uint::try_from(params_buf.len())?,
365        };
366
367        let pub_key = nss_sys::SECKEYPublicKey {
368            arena: ptr::null_mut(),
369            keyType: nss_sys::KeyType::ecKey as u32,
370            pkcs11Slot: ptr::null_mut(),
371            pkcs11ID: nss_sys::CK_INVALID_HANDLE.into(),
372            u: nss_sys::SECKEYPublicKeyStr_u {
373                ec: nss_sys::SECKEYECPublicKey {
374                    DEREncodedParams: params,
375                    publicValue: key_data,
376                    encoding: nss_sys::ECPointEncoding::ECPoint_Uncompressed as u32,
377                    size: 0,
378                },
379            },
380        };
381        Ok(Self::from(curve, unsafe {
382            PK11PublicKey::from_ptr(nss_sys::SECKEY_CopyPublicKey(&pub_key))?
383        }))
384    }
385}
386
387fn check_pub_key_bytes(bytes: &[u8], curve: Curve) -> Result<()> {
388    let field_len = curve.get_field_len();
389    // Check length of uncompressed point coordinates. There are 2 field elements
390    // and a leading "point form" octet (which must be EC_POINT_FORM_UNCOMPRESSED).
391    if bytes.len() != usize::try_from(2 * field_len + 1)? {
392        return Err(ErrorKind::InternalError.into());
393    }
394    // No support for compressed points.
395    if bytes[0] != u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)? {
396        return Err(ErrorKind::InternalError.into());
397    }
398    Ok(())
399}
400
401fn create_ec_params_for_curve(curve: Curve) -> Result<Vec<u8>> {
402    // The following code is adapted from:
403    // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/WebCryptoCommon.h#299
404    let curve_oid_tag = match curve {
405        Curve::P256 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP256R1,
406        Curve::P384 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP384R1,
407    };
408    // Retrieve curve data by OID tag.
409    let oid_data = unsafe { nss_sys::SECOID_FindOIDByTag(curve_oid_tag as u32) };
410    if oid_data.is_null() {
411        return Err(ErrorKind::InternalError.into());
412    }
413    // Set parameters
414    let oid_data_len = unsafe { (*oid_data).oid.len };
415    let mut buf = vec![0u8; usize::try_from(oid_data_len)? + 2];
416    buf[0] = c_uchar::try_from(nss_sys::SEC_ASN1_OBJECT_ID)?;
417    buf[1] = c_uchar::try_from(oid_data_len)?;
418    let oid_data_data =
419        unsafe { std::slice::from_raw_parts((*oid_data).oid.data, usize::try_from(oid_data_len)?) };
420    buf[2..].copy_from_slice(oid_data_data);
421    Ok(buf)
422}