identity.rs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use anyhow::Context;
  5. use once_cell_regex::regex;
  6. use std::{collections::BTreeSet, path::Path, process::Command};
  7. use x509_certificate::certificate::X509Certificate;
  8. use crate::Result;
  9. fn get_pem_list(keychain_path: &Path, name_substr: &str) -> std::io::Result<std::process::Output> {
  10. Command::new("security")
  11. .arg("find-certificate")
  12. .args(["-p", "-a"])
  13. .arg("-c")
  14. .arg(name_substr)
  15. .arg(keychain_path)
  16. .stderr(os_pipe::dup_stderr().unwrap())
  17. .output()
  18. }
  19. #[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
  20. pub struct Team {
  21. pub name: String,
  22. pub certificate_name: String,
  23. pub id: String,
  24. pub cert_prefix: &'static str,
  25. }
  26. impl Team {
  27. fn from_x509(cert_prefix: &'static str, cert: X509Certificate) -> Result<Self> {
  28. let common_name = cert
  29. .subject_common_name()
  30. .ok_or_else(|| anyhow::anyhow!("skipping cert, missing common name"))?;
  31. let organization = cert
  32. .subject_name()
  33. .iter_organization()
  34. .next()
  35. .and_then(|v| v.to_string().ok());
  36. let name = if let Some(organization) = organization {
  37. println!(
  38. "found cert {:?} with organization {:?}",
  39. common_name, organization
  40. );
  41. organization
  42. } else {
  43. println!(
  44. "found cert {:?} but failed to get organization; falling back to displaying common name",
  45. common_name
  46. );
  47. regex!(r"Apple Develop\w+: (.*) \(.+\)")
  48. .captures(&common_name)
  49. .map(|caps| caps[1].to_owned())
  50. .unwrap_or_else(|| {
  51. println!("regex failed to capture nice part of name in cert {:?}; falling back to displaying full name", common_name);
  52. common_name.clone()
  53. })
  54. };
  55. let id = cert
  56. .subject_name()
  57. .iter_organizational_unit()
  58. .next()
  59. .and_then(|v| v.to_string().ok())
  60. .ok_or_else(|| anyhow::anyhow!("skipping cert {common_name}: missing Organization Unit"))?;
  61. Ok(Self {
  62. name,
  63. certificate_name: common_name,
  64. id,
  65. cert_prefix,
  66. })
  67. }
  68. pub fn certificate_name(&self) -> String {
  69. self.certificate_name.clone()
  70. }
  71. }
  72. pub fn list(keychain_path: &Path) -> Result<Vec<Team>> {
  73. let certs = {
  74. let mut certs = Vec::new();
  75. for cert_prefix in [
  76. "iOS Distribution:",
  77. "Apple Distribution:",
  78. "Developer ID Application:",
  79. "Mac App Distribution:",
  80. "Apple Development:",
  81. "iOS App Development:",
  82. "Mac Development:",
  83. ] {
  84. let pem_list_out =
  85. get_pem_list(keychain_path, cert_prefix).context("Failed to call `security` command")?;
  86. let cert_list = X509Certificate::from_pem_multiple(pem_list_out.stdout)
  87. .context("Failed to parse X509 cert")?;
  88. certs.extend(cert_list.into_iter().map(|cert| (cert_prefix, cert)));
  89. }
  90. certs
  91. };
  92. Ok(
  93. certs
  94. .into_iter()
  95. .flat_map(|(cert_prefix, cert)| {
  96. Team::from_x509(cert_prefix, cert).map_err(|err| {
  97. eprintln!("{}", err);
  98. err
  99. })
  100. })
  101. // Silly way to sort this and ensure no dupes
  102. .collect::<BTreeSet<_>>()
  103. .into_iter()
  104. .collect(),
  105. )
  106. }