Skip to main content

rustica_keys/ssh/
cert.rs

1use std::collections::HashMap;
2use std::fmt;
3use std::fs::File;
4use std::io::Read;
5use std::path::Path;
6
7use ring::signature::{
8    ECDSA_P256_SHA256_FIXED,
9    ECDSA_P384_SHA384_FIXED,
10    RSA_PKCS1_2048_8192_SHA256,
11    RSA_PKCS1_2048_8192_SHA512,
12    ED25519,
13    UnparsedPublicKey,
14    RsaPublicKeyComponents};
15
16use ring::rand::{SystemRandom, SecureRandom};
17
18// TODO @obelisk undo this Result aliasing
19use super::error::{Error, ErrorKind, Result};
20use super::keytype::{KeyType};
21use super::pubkey::{PublicKey, PublicKeyKind};
22use super::reader::Reader;
23
24use std::convert::TryFrom;
25
26/// Represents the different types a certificate can be.
27#[derive(Debug, PartialEq, Clone, Copy)]
28pub enum CertType {
29    /// Represents a user certificate.
30    User = 1,
31
32    /// Represents a host certificate.
33    Host = 2,
34}
35
36impl TryFrom<&str> for CertType {
37    type Error = &'static str;
38
39    fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
40        match s {
41            "user" | "User" => Ok(CertType::User),
42            "host" | "Host" => Ok(CertType::Host),
43            _ => Err("Unknown certificate type"),
44        }
45    }
46}
47
48impl fmt::Display for CertType {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        match *self {
51            CertType::User => write!(f, "user certificate"),
52            CertType::Host => write!(f, "host certificate"),
53        }
54    }
55}
56
57const STANDARD_EXTENSIONS: [(&str, &str); 5] = [
58    ("permit-agent-forwarding", ""),
59    ("permit-port-forwarding", ""),
60    ("permit-pty", ""),
61    ("permit-user-rc", ""),
62    ("permit-X11-forwarding", ""),
63];
64
65impl From<Extensions> for HashMap<String, String> {
66    fn from(extensions: Extensions) -> Self {
67        match extensions {
68            Extensions::Standard => {
69                let mut hm = HashMap::new();
70                for extension in &STANDARD_EXTENSIONS {
71                    hm.insert(String::from(extension.0), String::from(extension.1));
72                }
73                hm
74            },
75            Extensions::Custom(co) => co,
76        }
77    }
78}
79
80/// Type that encapsulates the normal usage of the extensions field.
81#[derive(Debug)]
82pub enum Extensions {
83    /// Contains the five standard extensions: agent-forwarding, port-forwarding, pty, user-rc, X11-forwarding
84    Standard,
85    /// Allows a completely custom set of extensions to be passed in. This does not contain the standard
86    /// extensions
87    Custom(HashMap<String, String>)
88}
89
90/// Type that encapsulates the normal usage of the critical options field.
91/// I used a structure instead of an Option for consistency and possible future
92/// expansion into a ForceCommand type.
93#[derive(Debug)]
94pub enum CriticalOptions {
95    /// Don't use any critical options
96    None,
97    /// Allows a custom set of critical options. Does not contain any standard options.
98    Custom(HashMap<String, String>)
99}
100
101impl From<CriticalOptions> for HashMap<String, String> {
102    fn from(critical_options: CriticalOptions) -> Self {
103        match critical_options {
104            CriticalOptions::None => HashMap::new(),
105            CriticalOptions::Custom(co) => co,
106        }
107    }
108}
109
110/// A type which represents an OpenSSH certificate key.
111/// Please refer to [PROTOCOL.certkeys] for more details about OpenSSH certificates.
112/// [PROTOCOL.certkeys]: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
113#[derive(Debug)]
114pub struct Certificate {
115    /// Type of key.
116    pub key_type: KeyType,
117
118    /// Cryptographic nonce.
119    pub nonce: Vec<u8>,
120
121    /// Public key part of the certificate.
122    pub key: PublicKey,
123
124    /// Serial number of certificate.
125    pub serial: u64,
126
127    /// Represents the type of the certificate.
128    pub cert_type: CertType,
129
130    /// Key identity.
131    pub key_id: String,
132
133    /// The list of valid principals for the certificate.
134    pub principals: Vec<String>,
135
136    /// Time after which certificate is considered as valid.
137    pub valid_after: u64,
138
139    /// Time before which certificate is considered as valid.
140    pub valid_before: u64,
141
142    /// Critical options of the certificate. Generally used to
143    /// control features which restrict access.
144    pub critical_options: HashMap<String, String>,
145
146    /// Certificate extensions. Extensions are usually used to
147    /// enable features that grant access.
148    pub extensions: HashMap<String, String>,
149
150    /// The `reserved` field is currently unused and is ignored in this version of the protocol.
151    pub reserved: Vec<u8>,
152
153    /// Signature key contains the CA public key used to sign the certificate.
154    pub signature_key: PublicKey,
155
156    /// Signature of the certificate.
157    pub signature: Vec<u8>,
158
159    /// Associated comment, if any.
160    pub comment: Option<String>,
161
162    /// The entire serialized certificate, used for exporting
163    pub serialized: Vec<u8>,
164}
165
166impl Certificate {
167    /// Reads an OpenSSH certificate from a given path.
168    ///
169    /// # Example
170    ///
171    /// ```rust
172    /// # use rustica_keys::Certificate;
173    /// # fn example() {
174    ///     let cert = Certificate::from_path("/path/to/id_ed25519-cert.pub").unwrap();
175    ///     println!("{}", cert);
176    /// # }
177    /// ```
178    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Certificate> {
179        let mut contents = String::new();
180        File::open(path)?.read_to_string(&mut contents)?;
181
182        Certificate::from_string(&contents)
183    }
184
185    /// Reads an OpenSSH certificate from a given string.
186    ///
187    /// # Example
188    ///
189    /// ```rust
190    /// # use rustica_keys::Certificate;
191    /// # fn example() {
192    ///     let cert = Certificate::from_string("ssh-rsa AAAAB3NzaC1yc2EAAAA...").unwrap();
193    ///     println!("{}", cert);
194    /// # }
195    /// ```
196    pub fn from_string(s: &str) -> Result<Certificate> {
197        let mut iter = s.split_whitespace();
198
199        let kt_name = iter
200            .next()
201            .ok_or_else(|| Error::with_kind(ErrorKind::InvalidFormat))?;
202
203        let key_type = KeyType::from_name(&kt_name)?;
204        if !key_type.is_cert {
205            return Err(Error::with_kind(ErrorKind::NotCertificate));
206        }
207
208        let data = iter
209            .next()
210            .ok_or_else(|| Error::with_kind(ErrorKind::InvalidFormat))?;
211
212        let comment = iter.next().map(String::from);
213        let decoded = base64::decode(&data)?;
214        let mut reader = Reader::new(&decoded);
215
216        // Validate key types before reading the rest of the data
217        let kt_from_reader = reader.read_string()?;
218        if kt_name != kt_from_reader {
219            return Err(Error::with_kind(ErrorKind::KeyTypeMismatch));
220        }
221
222        let nonce = reader.read_bytes()?;
223        let key = PublicKey::from_reader(&kt_name, &mut reader)?;
224        let serial = reader.read_u64()?;
225
226        let cert_type = match reader.read_u32()? {
227            1 => CertType::User,
228            2 => CertType::Host,
229            n => return Err(Error::with_kind(ErrorKind::InvalidCertType(n))),
230        };
231
232        let key_id = reader.read_string()?;
233        let principals = reader.read_bytes().and_then(|v| read_principals(&v))?;
234        let valid_after = reader.read_u64()?;
235        let valid_before = reader.read_u64()?;
236        let critical_options = reader.read_bytes().and_then(|v| read_options(&v))?;
237        let extensions = reader.read_bytes().and_then(|v| read_options(&v))?;
238        let reserved = reader.read_bytes()?;
239        let signature_key = reader
240            .read_bytes()
241            .and_then(|v| PublicKey::from_bytes(&v))?;
242
243        let signed_len = reader.get_offset();
244        let signature = reader.read_bytes()?;
245
246        reader.set_offset(0).unwrap();
247        let signed_bytes = reader.read_raw_bytes(signed_len).unwrap();
248
249        // Verify the certificate is properly signed
250        verify_signature(&signature, &signed_bytes, &signature_key)?;
251
252        let cert = Certificate {
253            key_type,
254            nonce,
255            key,
256            serial,
257            cert_type,
258            key_id,
259            principals,
260            valid_after,
261            valid_before,
262            critical_options,
263            extensions,
264            reserved,
265            signature_key,
266            signature,
267            comment,
268            serialized: decoded,
269        };
270
271        Ok(cert)
272    }
273
274    /// Create a new SSH certificate from the provided values. It takes
275    /// two function pointers to retrieve the signing public key as well
276    /// as a function to do the actual signing. This function pointed to is 
277    /// responsible for hashing the data as no hashing is done Certificate::new
278    ///
279    /// # Example
280    ///
281    /// ```rust
282    /// # use rustica_keys::{Certificate, PublicKey};
283    /// # use rustica_keys::ssh::{CertType, CriticalOptions, Extensions};
284    /// fn test_signer(buf: &[u8]) -> Option<Vec<u8>> { None }
285    /// fn test_pubkey() -> Option<Vec<u8>> { None }
286    /// # fn example() {
287    ///   let cert = Certificate::new(
288    ///      PublicKey::from_string("AAA...").unwrap(),
289    ///      CertType::User,
290    ///      0xFEFEFEFEFEFEFEFE,
291    ///      String::from("obelisk@exclave"),
292    ///      vec![String::from("obelisk2")],
293    ///      0,
294    ///      0xFFFFFFFFFFFFFFFF,
295    ///      CriticalOptions::None,
296    ///      Extensions::Standard,
297    ///      PublicKey::from_string("AAA...").unwrap(),
298    ///      test_signer,
299    ///   );
300    /// 
301    ///   match cert {
302    ///      Ok(cert) => println!("{}", cert),
303    ///      Err(e) => println!("Encountered an error while creating certificate: {}", e),
304    ///   }
305    /// # }
306    /// ```
307    #[allow(clippy::too_many_arguments)]
308    pub fn new(
309        pubkey: PublicKey,
310        cert_type: CertType,
311        serial: u64,
312        key_id: String,
313        principals: Vec<String>,
314        valid_after: u64,
315        valid_before: u64,
316        critical_options: CriticalOptions,
317        extensions: Extensions,
318        ca_pubkey: PublicKey,
319        signer: impl Fn(&[u8]) -> Option<Vec<u8>>,
320    ) -> Result<Certificate> {
321        let mut writer = super::Writer::new();
322        let kt_name = format!("{}-cert-v01@openssh.com", pubkey.key_type.name);
323        // Write the cert type
324        writer.write_string(kt_name.as_str());
325        
326        // Generate the nonce
327        let mut nonce = [0x0u8; 32];
328        let rng = SystemRandom::new();
329        match SecureRandom::fill(&rng, &mut nonce) {
330            Ok(()) => (),
331            Err(_) => return Err(Error::with_kind(ErrorKind::UnexpectedEof)),
332        };
333        // Write the nonce
334        writer.write_bytes(&nonce);
335
336        // Write the user public key
337        writer.write_pub_key(&pubkey);
338
339        // Write the serial number
340        writer.write_u64(serial);
341
342        // Write what kind of cert this is
343        writer.write_u32(cert_type as u32);
344
345        // Write the key id
346        writer.write_string(&key_id);
347
348        // Write the principals
349        writer.write_string_vec(&principals);
350
351        // Write valid after
352        writer.write_u64(valid_after);
353
354        // Write valid before
355        writer.write_u64(valid_before);
356
357        // Write critical options
358        let critical_options = match critical_options {
359            CriticalOptions::None => {
360                writer.write_string_map(&HashMap::new());
361                HashMap::new()
362            },
363            CriticalOptions::Custom(co) => {
364                writer.write_string_map(&co);
365                co
366            },
367        };
368
369        // Write extensions
370        let extensions = match extensions {
371            Extensions::Standard => {
372                let stdex = STANDARD_EXTENSIONS.iter().map(|x| (String::from(x.0), String::from(x.1))).collect();
373                writer.write_string_map(&stdex);
374                stdex
375            },
376            Extensions::Custom(co) => {
377                writer.write_string_map(&co);
378                co
379            },
380        };
381
382        // Write the unused reserved bytes
383        writer.write_u32(0x0);
384
385        // Write the CA public key
386        writer.write_bytes(&ca_pubkey.encode());
387
388        // Sign the data and write it to the cert
389        let signature =  match signer(writer.as_bytes()) {
390            Some(sig) => sig,
391            None => return Err(Error::with_kind(ErrorKind::SigningError)),
392        };
393
394        match verify_signature(&signature, &writer.as_bytes(), &ca_pubkey) {
395            Ok(_) => (),
396            Err(e) => return Err(e),
397        }
398
399        writer.write_bytes(&signature);
400
401        Ok(Certificate {
402            key_type: KeyType::from_name(kt_name.as_str()).unwrap(),
403            nonce: nonce.to_vec(),
404            key: pubkey,
405            serial,
406            cert_type,
407            key_id,
408            principals,
409            valid_after,
410            valid_before,
411            critical_options,
412            extensions,
413            reserved: vec![0,0,0,0,0,0,0,0],
414            signature_key: ca_pubkey,
415            signature,
416            comment: None,
417            serialized: writer.into_bytes(),
418        })
419    }
420}
421
422// Reads `option` values from a byte sequence.
423// The `option` values are used to represent the `critical options` and
424// `extensions` in an OpenSSH certificate key, which are represented as tuples
425// containing the `name` and `data` values of type `string`.
426// Some `options` are `flags` only (e.g. the certificate extensions) and the
427// associated value with them is the empty string (""), while others are `string`
428// options and have an associated value, which is a `string`.
429// The `critical options` of a certificate are always `string` options, since they
430// have an associated `string` value, which is embedded in a separate buffer, so
431// in order to extract the associated value we need to read the buffer first and then
432// read the `string` value itself.
433fn read_options(buf: &[u8]) -> Result<HashMap<String, String>> {
434    let mut reader = Reader::new(&buf);
435    let mut options = HashMap::new();
436
437    // Use a `Reader` and loop until EOF is reached, so that we can
438    // read all options from the provided byte slice.
439    loop {
440        let name = match reader.read_string() {
441            Ok(v) => v,
442            Err(e) => match e.kind {
443                ErrorKind::UnexpectedEof => break,
444                _ => return Err(e),
445            },
446        };
447
448        // If we have a `string` option extract the value from the buffer,
449        // otherwise we have a `flag` option which is the `empty` string.
450        let value_buf = reader.read_bytes()?;
451        let value = if !value_buf.is_empty() {
452            Reader::new(&value_buf).read_string()?
453        } else {
454            "".to_string()
455        };
456
457        options.insert(name, value);
458    }
459
460    Ok(options)
461}
462
463// Reads the `principals` field of a certificate key.
464// The `principals` are represented as a sequence of `string` values
465// embedded in a buffer.
466// This function reads the whole byte slice until EOF is reached in order to
467// ensure all principals are read from the byte slice.
468fn read_principals(buf: &[u8]) -> Result<Vec<String>> {
469    let mut reader = Reader::new(&buf);
470    let mut items = Vec::new();
471
472    loop {
473        let principal = match reader.read_string() {
474            Ok(v) => v,
475            Err(e) => match e.kind {
476                ErrorKind::UnexpectedEof => break,
477                _ => return Err(e),
478            },
479        };
480
481        items.push(principal);
482    }
483
484    Ok(items)
485}
486
487// Verifies the certificate's signature is valid.
488// Appended to the end of every SSH Cert is a signature for the preceding data,
489// depending on the key, the signature could be any of the following:
490//
491// ECDSA
492//  ecdsa-sha2-nistp256
493//  ecdsa-sha2-nistp384
494//  ecdsa-sha2-nistp521 (but this is unsupported in Ring so not supported here)
495//
496// RSA
497//  rsa-sha2-256
498//  rsa-sha2-512
499//
500// Ed25519
501//
502// We then take the public key of the CA (immiediately preceeding the signature and part of the signed data)
503// and verify the signature accordingly. If the signature is not valid, this function errors.
504fn verify_signature(signature_buf: &[u8], signed_bytes: &[u8], public_key: &PublicKey) -> Result<Vec<u8>> {
505    let mut reader = Reader::new(&signature_buf);
506    let sig_type = reader.read_string().and_then(|v| KeyType::from_name(&v))?;
507
508    match &public_key.kind {
509         PublicKeyKind::Ecdsa(key) => {
510            let sig_reader = reader.read_bytes()?;
511            let mut reader = Reader::new(&sig_reader);
512
513            // Read the R value
514            let mut sig = reader.read_mpint()?;
515            // Read the S value
516            sig.extend(reader.read_mpint()?);
517
518            let alg = match sig_type.name {
519                "ecdsa-sha2-nistp256" => &ECDSA_P256_SHA256_FIXED,
520                "ecdsa-sha2-nistp384" => &ECDSA_P384_SHA384_FIXED,
521                _ => return Err(Error::with_kind(ErrorKind::KeyTypeMismatch)), 
522            };
523
524            let result = UnparsedPublicKey::new(alg, &key.key).verify(&signed_bytes, &sig);
525            match result {
526                Ok(()) => Ok(signature_buf.to_vec()),
527                Err(_) => Err(Error::with_kind(ErrorKind::CertificateInvalidSignature)),
528            }
529        },
530        PublicKeyKind::Rsa(key) => {
531            let alg = match sig_type.name {
532                "rsa-sha2-256" => &RSA_PKCS1_2048_8192_SHA256,
533                "rsa-sha2-512" => &RSA_PKCS1_2048_8192_SHA512,
534                _ => return Err(Error::with_kind(ErrorKind::KeyTypeMismatch)), 
535            };
536            let signature = reader.read_bytes()?;
537            let public_key = RsaPublicKeyComponents { n: &key.n, e: &key.e };
538            let result = public_key.verify(alg, &signed_bytes, &signature);
539            match result {
540                Ok(()) => Ok(signature_buf.to_vec()),
541                Err(e) => {
542                    println!("Error: {}", e);
543                    Err(Error::with_kind(ErrorKind::CertificateInvalidSignature))
544                }
545            }
546        },
547        PublicKeyKind::Ed25519(key) => {
548            let alg = &ED25519;
549            let signature = reader.read_bytes()?;
550            let peer_public_key = UnparsedPublicKey::new(alg, &key.key);
551            match peer_public_key.verify(&signed_bytes, &signature) {
552                Ok(()) => Ok(signature_buf.to_vec()),
553                Err(e) => {
554                    println!("Error: {}", e);
555                    Err(Error::with_kind(ErrorKind::CertificateInvalidSignature))
556                }
557            }
558        },
559    }
560}
561
562impl fmt::Display for Certificate {
563    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
564        if !f.alternate() {
565            write!(f, "{} {} {}", &self.key_type.name, base64::encode(&self.serialized), &self.key_id)
566        } else {
567            writeln!(f, "Type: {} {}", self.key_type, self.cert_type).unwrap();
568            writeln!(f, "Public Key: {} {}:{}", self.key_type.short_name, self.key.fingerprint().kind, self.key.fingerprint().hash).unwrap();
569            writeln!(f, "Signing CA: {} {}:{} (using {})", self.signature_key.key_type.short_name, self.signature_key.fingerprint().kind, self.signature_key.fingerprint().hash, self.signature_key.key_type).unwrap();
570            writeln!(f, "Key ID: \"{}\"", self.key_id).unwrap();
571            writeln!(f, "Serial: {}", self.serial).unwrap();
572
573            if self.valid_before == 0xFFFFFFFFFFFFFFFF && self.valid_after == 0x0 {
574                writeln!(f, "Valid: forever").unwrap();
575            } else {
576                writeln!(f, "Valid between: {} and {}", self.valid_after, self.valid_before).unwrap();
577            }
578
579            if self.principals.is_empty() {
580                writeln!(f, "Principals: (none)").unwrap();
581            } else {
582                writeln!(f, "Principals:").unwrap();
583                for principal in &self.principals {
584                    writeln!(f, "\t{}", principal).unwrap();
585                }
586            }
587
588            if self.critical_options.is_empty() {
589                writeln!(f, "Critical Options: (none)").unwrap();
590            } else {
591                writeln!(f, "Critical Options:").unwrap();
592                for (name, value) in &self.critical_options {
593                    writeln!(f, "\t{} {}", name, value).unwrap();
594                }
595            }
596
597            if self.extensions.is_empty() {
598                writeln!(f, "Extensions: (none)").unwrap();
599            } else {
600                writeln!(f, "Extensions:").unwrap();
601                for name in self.extensions.keys() {
602                    writeln!(f, "\t{}", name).unwrap();
603                }
604            }
605
606            write!(f, "")
607        }
608    }
609}