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

google / OpenSK / 4894858697

pending completion
4894858697

push

github

GitHub
Credential wrapping in Env (#624)

604 of 604 new or added lines in 4 files covered. (100.0%)

12659 of 13162 relevant lines covered (96.18%)

6273.63 hits per line

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

98.03
/libraries/opensk/src/api/private_key.rs
1
// Copyright 2021-2023 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
use crate::api::crypto::ecdsa::{SecretKey as _, Signature};
16
use crate::api::key_store::KeyStore;
17
use crate::ctap::data_formats::{extract_array, extract_byte_string, CoseKey, SignatureAlgorithm};
18
use crate::ctap::secret::Secret;
19
use crate::ctap::status_code::Ctap2StatusCode;
20
use crate::env::{EcdsaSk, Env};
21
use alloc::vec;
22
use alloc::vec::Vec;
23
use core::convert::TryFrom;
24
use core::ops::Deref;
25
#[cfg(feature = "ed25519")]
26
use core::ops::DerefMut;
27
#[cfg(feature = "ed25519")]
28
use rand_core::RngCore;
29
use sk_cbor as cbor;
30
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
31

32
/// An asymmetric private key that can sign messages.
33
#[derive(Clone, Debug)]
186✔
34
// We shouldn't compare private keys in prod without constant-time operations.
35
#[cfg_attr(test, derive(PartialEq, Eq))]
34✔
36
pub enum PrivateKey {
37
    // We store the seed instead of the key since we can't get the seed back from the key. We could
38
    // store both if we believe deriving the key is done more than once and costly.
39
    Ecdsa(Secret<[u8; 32]>),
40
    #[cfg(feature = "ed25519")]
41
    Ed25519(ed25519_compact::SecretKey),
42
}
43

44
impl PrivateKey {
45
    /// Creates a new private key for the given algorithm.
46
    ///
47
    /// # Panics
48
    ///
49
    /// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
50
    pub fn new(env: &mut impl Env, alg: SignatureAlgorithm) -> Self {
1,120✔
51
        match alg {
1,120✔
52
            SignatureAlgorithm::Es256 => {
53
                PrivateKey::Ecdsa(env.key_store().generate_ecdsa_seed().unwrap())
1,106✔
54
            }
55
            #[cfg(feature = "ed25519")]
56
            SignatureAlgorithm::Eddsa => {
57
                let mut bytes: Secret<[u8; 32]> = Secret::default();
14✔
58
                env.rng().fill_bytes(bytes.deref_mut());
14✔
59
                Self::new_ed25519_from_bytes(&*bytes).unwrap()
14✔
60
            }
61
            SignatureAlgorithm::Unknown => unreachable!(),
×
62
        }
63
    }
1,120✔
64

65
    /// Creates a new ecdsa private key.
66
    pub fn new_ecdsa(env: &mut impl Env) -> PrivateKey {
972✔
67
        Self::new(env, SignatureAlgorithm::Es256)
972✔
68
    }
972✔
69

70
    /// Helper function that creates a private key of type ECDSA.
71
    ///
72
    /// This function is public for legacy credential source parsing only.
73
    pub fn new_ecdsa_from_bytes(bytes: &[u8]) -> Option<Self> {
91,872✔
74
        if bytes.len() != 32 {
91,872✔
75
            return None;
8✔
76
        }
91,864✔
77
        let mut seed: Secret<[u8; 32]> = Secret::default();
91,864✔
78
        seed.copy_from_slice(bytes);
91,864✔
79
        Some(PrivateKey::Ecdsa(seed))
91,864✔
80
    }
91,872✔
81

82
    #[cfg(feature = "ed25519")]
83
    pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
28✔
84
        if bytes.len() != 32 {
28✔
85
            return None;
8✔
86
        }
20✔
87
        let seed = ed25519_compact::Seed::from_slice(bytes).unwrap();
20✔
88
        Some(Self::Ed25519(ed25519_compact::KeyPair::from_seed(seed).sk))
20✔
89
    }
28✔
90

91
    /// Returns the ECDSA private key.
92
    pub fn ecdsa_key<E: Env>(&self, env: &mut E) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
22✔
93
        match self {
22✔
94
            PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed),
22✔
95
            #[allow(unreachable_patterns)]
96
            _ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
×
97
        }
98
    }
22✔
99

100
    /// Returns the corresponding public key.
101
    pub fn get_pub_key(&self, env: &mut impl Env) -> Result<CoseKey, Ctap2StatusCode> {
104✔
102
        Ok(match self {
104✔
103
            PrivateKey::Ecdsa(ecdsa_seed) => {
104✔
104
                CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key())
104✔
105
            }
106
            #[cfg(feature = "ed25519")]
107
            PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
×
108
        })
109
    }
104✔
110

111
    /// Returns the encoded signature for a given message.
112
    pub(crate) fn sign_and_encode(
136✔
113
        &self,
136✔
114
        env: &mut impl Env,
136✔
115
        message: &[u8],
136✔
116
    ) -> Result<Vec<u8>, Ctap2StatusCode> {
136✔
117
        Ok(match self {
136✔
118
            PrivateKey::Ecdsa(ecdsa_seed) => {
136✔
119
                ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der()
136✔
120
            }
121
            #[cfg(feature = "ed25519")]
122
            PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
×
123
        })
124
    }
136✔
125

126
    /// The associated COSE signature algorithm identifier.
127
    pub fn signature_algorithm(&self) -> SignatureAlgorithm {
1,164✔
128
        match self {
1,164✔
129
            PrivateKey::Ecdsa(_) => SignatureAlgorithm::Es256,
1,152✔
130
            #[cfg(feature = "ed25519")]
131
            PrivateKey::Ed25519(_) => SignatureAlgorithm::Eddsa,
12✔
132
        }
133
    }
1,164✔
134

135
    /// Writes the key bytes.
136
    pub fn to_bytes(&self) -> Secret<[u8]> {
1,176✔
137
        let mut bytes = Secret::new(32);
1,176✔
138
        match self {
1,176✔
139
            PrivateKey::Ecdsa(ecdsa_seed) => bytes.copy_from_slice(ecdsa_seed.deref()),
1,164✔
140
            #[cfg(feature = "ed25519")]
141
            PrivateKey::Ed25519(ed25519_key) => bytes.copy_from_slice(ed25519_key.seed().deref()),
12✔
142
        }
143
        bytes
1,176✔
144
    }
1,176✔
145
}
146

147
fn ecdsa_key_from_seed<E: Env>(
262✔
148
    env: &mut E,
262✔
149
    seed: &[u8; 32],
262✔
150
) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
262✔
151
    let ecdsa_bytes = env.key_store().derive_ecdsa(seed)?;
262✔
152
    Ok(EcdsaSk::<E>::from_slice(&ecdsa_bytes).unwrap())
262✔
153
}
262✔
154

155
impl From<&PrivateKey> for cbor::Value {
156
    /// Writes a private key into CBOR format. This exposes the cryptographic secret.
157
    // TODO needs zeroization if seed is secret
158
    // called in wrap_credential and PublicKeyCredentialSource
159
    fn from(private_key: &PrivateKey) -> Self {
1,160✔
160
        cbor_array![
1,160✔
161
            cbor_int!(private_key.signature_algorithm() as i64),
1,160✔
162
            cbor_bytes!(private_key.to_bytes().expose_secret_to_vec()),
1,160✔
163
        ]
1,160✔
164
    }
1,160✔
165
}
166

167
impl TryFrom<cbor::Value> for PrivateKey {
168
    type Error = Ctap2StatusCode;
169

170
    fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
91,802✔
171
        let mut array = extract_array(cbor_value)?;
91,802✔
172
        if array.len() != 2 {
91,802✔
173
            return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
4✔
174
        }
91,798✔
175
        let key_bytes = extract_byte_string(array.pop().unwrap())?;
91,798✔
176
        match SignatureAlgorithm::try_from(array.pop().unwrap())? {
91,798✔
177
            SignatureAlgorithm::Es256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
91,858✔
178
                .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
91,858✔
179
            #[cfg(feature = "ed25519")]
180
            SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&key_bytes)
4✔
181
                .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
4✔
182
            _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
2✔
183
        }
184
    }
91,868✔
185
}
186

187
#[cfg(test)]
188
mod test {
189
    use super::*;
190
    use crate::env::test::TestEnv;
191

192
    #[test]
2✔
193
    fn test_new_ecdsa_from_bytes() {
2✔
194
        let mut env = TestEnv::default();
2✔
195
        let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
2✔
196
        let key_bytes = private_key.to_bytes();
2✔
197
        assert_eq!(
2✔
198
            PrivateKey::new_ecdsa_from_bytes(&key_bytes),
2✔
199
            Some(private_key)
2✔
200
        );
2✔
201
    }
2✔
202

203
    #[test]
2✔
204
    #[cfg(feature = "ed25519")]
205
    fn test_new_ed25519_from_bytes() {
2✔
206
        let mut env = TestEnv::default();
2✔
207
        let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Eddsa);
2✔
208
        let key_bytes = private_key.to_bytes();
2✔
209
        assert_eq!(
2✔
210
            PrivateKey::new_ed25519_from_bytes(&key_bytes),
2✔
211
            Some(private_key)
2✔
212
        );
2✔
213
    }
2✔
214

215
    #[test]
2✔
216
    fn test_new_ecdsa_from_bytes_wrong_length() {
2✔
217
        assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None);
2✔
218
        assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 31]), None);
2✔
219
        assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 33]), None);
2✔
220
        assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None);
2✔
221
    }
2✔
222

223
    #[test]
2✔
224
    #[cfg(feature = "ed25519")]
225
    fn test_new_ed25519_from_bytes_wrong_length() {
2✔
226
        assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None);
2✔
227
        assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None);
2✔
228
        assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None);
2✔
229
        assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None);
2✔
230
    }
2✔
231

232
    #[test]
2✔
233
    fn test_private_key_get_pub_key() {
2✔
234
        let mut env = TestEnv::default();
2✔
235
        let private_key = PrivateKey::new_ecdsa(&mut env);
2✔
236
        let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
2✔
237
        let public_key = ecdsa_key.public_key();
2✔
238
        assert_eq!(
2✔
239
            private_key.get_pub_key(&mut env),
2✔
240
            Ok(CoseKey::from_ecdsa_public_key(public_key))
2✔
241
        );
2✔
242
    }
2✔
243

244
    #[test]
2✔
245
    fn test_private_key_sign_and_encode() {
2✔
246
        let mut env = TestEnv::default();
2✔
247
        let message = [0x5A; 32];
2✔
248
        let private_key = PrivateKey::new_ecdsa(&mut env);
2✔
249
        let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
2✔
250
        let signature = ecdsa_key.sign(&message).to_der();
2✔
251
        assert_eq!(
2✔
252
            private_key.sign_and_encode(&mut env, &message),
2✔
253
            Ok(signature)
2✔
254
        );
2✔
255
    }
2✔
256

257
    fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) {
4✔
258
        let mut env = TestEnv::default();
4✔
259
        let private_key = PrivateKey::new(&mut env, signature_algorithm);
4✔
260
        assert_eq!(private_key.signature_algorithm(), signature_algorithm);
4✔
261
    }
4✔
262

263
    #[test]
2✔
264
    fn test_ecdsa_private_key_signature_algorithm() {
2✔
265
        test_private_key_signature_algorithm(SignatureAlgorithm::Es256);
2✔
266
    }
2✔
267

268
    #[test]
2✔
269
    #[cfg(feature = "ed25519")]
270
    fn test_ed25519_private_key_signature_algorithm() {
2✔
271
        test_private_key_signature_algorithm(SignatureAlgorithm::Eddsa);
2✔
272
    }
2✔
273

274
    fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
4✔
275
        let mut env = TestEnv::default();
4✔
276
        let private_key = PrivateKey::new(&mut env, signature_algorithm);
4✔
277
        let cbor = cbor::Value::from(&private_key);
4✔
278
        assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),);
4✔
279
    }
4✔
280

281
    #[test]
2✔
282
    fn test_ecdsa_private_key_from_to_cbor() {
2✔
283
        test_private_key_from_to_cbor(SignatureAlgorithm::Es256);
2✔
284
    }
2✔
285

286
    #[test]
2✔
287
    #[cfg(feature = "ed25519")]
288
    fn test_ed25519_private_key_from_to_cbor() {
2✔
289
        test_private_key_from_to_cbor(SignatureAlgorithm::Eddsa);
2✔
290
    }
2✔
291

292
    fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
4✔
293
        let cbor = cbor_array![
4✔
294
            cbor_int!(signature_algorithm as i64),
4✔
295
            cbor_bytes!(vec![0x88; 32]),
4✔
296
            // The array is too long.
4✔
297
            cbor_int!(0),
4✔
298
        ];
4✔
299
        assert_eq!(
4✔
300
            PrivateKey::try_from(cbor),
4✔
301
            Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
4✔
302
        );
4✔
303
    }
4✔
304

305
    #[test]
2✔
306
    fn test_ecdsa_private_key_from_bad_cbor() {
2✔
307
        test_private_key_from_bad_cbor(SignatureAlgorithm::Es256);
2✔
308
    }
2✔
309

310
    #[test]
2✔
311
    #[cfg(feature = "ed25519")]
312
    fn test_ed25519_private_key_from_bad_cbor() {
2✔
313
        test_private_key_from_bad_cbor(SignatureAlgorithm::Eddsa);
2✔
314
    }
2✔
315

316
    #[test]
2✔
317
    fn test_private_key_from_bad_cbor_unsupported_algo() {
2✔
318
        let cbor = cbor_array![
2✔
319
            // This algorithms doesn't exist.
2✔
320
            cbor_int!(-1),
2✔
321
            cbor_bytes!(vec![0x88; 32]),
2✔
322
        ];
2✔
323
        assert_eq!(
2✔
324
            PrivateKey::try_from(cbor),
2✔
325
            Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
2✔
326
        );
2✔
327
    }
2✔
328
}
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

© 2026 Coveralls, Inc