diff --git a/Cargo.toml b/Cargo.toml index 35b82f04b47a2a67c2a9ca8ef0b05ec6f3f7b5fd..fc398a316f19b98cea1d8fe68be2f624788a84f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ Signatrust provides a safe and high throughput solution for signing linux packag documentation = "https://gitee.com/openeuler/signatrust/#readme" homepage = "https://gitee.com/openeuler/signatrust" keywords = ["rpm", "efi", "ko", "signature"] -license = "MulanPSL – 2.0" +license = "MulanPSL-2.0" readme = "README.md" repository = "https://gitee.com/openeuler/signatrust" @@ -70,6 +70,7 @@ url = "2.3.1" futures = "0.3.26" utoipa = { version = "3", features = ["actix_extras"] } utoipa-swagger-ui = { version ="3.1.3", features = ["actix-web"]} +efi_signer = "0.1.0" [build-dependencies] tonic-build = "0.8.4" @@ -91,4 +92,4 @@ name = "control-admin" path = "src/control_admin_entrypoint.rs" [profile.release] -strip = "debuginfo" \ No newline at end of file +strip = "debuginfo" diff --git a/docs/how to sign&verify a EFI image.md b/docs/how to sign&verify a EFI image.md new file mode 100644 index 0000000000000000000000000000000000000000..03a40fc3fecbd293184071c6ffb5b5e978f1988d --- /dev/null +++ b/docs/how to sign&verify a EFI image.md @@ -0,0 +1,144 @@ +# prerequisite +- create a x509 key in data server if you do not have one + ```bash + curl -X 'POST' \ + 'http://10.0.0.139:8080/api/v1/keys/' \ + -H 'accept: application/json' \ + -H 'Authorization: G2fmAfnLUT4R5TDaQhpCWvGznme0zaA0YQFBKJIc' \ + -H 'Content-Type: application/json' \ + -d '{ + "attributes": { + "digest_algorithm": "sha2_256", + "key_length": "4096", + "key_type": "rsa", + "common_name": "EFI signer", + "country_name": "CN", + "locality": "Chengdu", + "organization": "openEuler", + "organizational_unit": "infra", + "province_name": "Sichuan" + }, + "description": "a test x509 key pair", + "expire_at": "2024-05-12 22:10:57+08:00", + "key_type": "x509", + "name": "my-x509" + }' + ``` +- export the x509 certificate into PEM format + - get the key id + ``` + curl -X 'GET' \ + 'http://10.0.0.139:8080/api/v1/keys/' \ + -H 'accept: application/json' \ + -H 'Authorization: G2fmAfnLUT4R5TDaQhpCWvGznme0zaA0YQFBKJIc' + ``` + + ``` + [ + { + "id": 5, + "name": "my-x509", + "email": "tommylikehu@gmail.com", + "description": "a test x509 key pair", + "user": 1, + "attributes": { + "common_name": "EFI signer", + "country_name": "CN", + "create_at": "2023-05-04 09:24:01.488589752 UTC", + "digest_algorithm": "sha2_256", + "expire_at": "2024-05-12 22:10:57+08:00", + "key_length": "4096", + "key_type": "rsa", + "locality": "Chengdu", + "name": "my-x509", + "organization": "openEuler", + "organizational_unit": "infra", + "province_name": "Sichuan" + }, + "key_type": "x509", + "fingerprint": "2A8853F8411F4B243FB424F90B2541D7AE5AF8C9", + "create_at": "2023-05-04 09:24:01 UTC", + "expire_at": "2024-05-12 14:10:57 UTC", + "key_state": "disabled" + } + ] + ``` + - enable the key + ``` + curl -X 'POST' \ + 'http://10.0.0.139:8080/api/v1/keys/5/enable' \ + -H 'accept: */*' \ + -H 'Authorization: G2fmAfnLUT4R5TDaQhpCWvGznme0zaA0YQFBKJIc' \ + -d '' + ``` + - get key certificate by id + ``` + curl -X 'POST' \ + 'http://10.0.0.139:8080/api/v1/keys/5/export' \ + -H 'accept: application/json' \ + -H 'Authorization: G2fmAfnLUT4R5TDaQhpCWvGznme0zaA0YQFBKJIc' \ + -d '' + ``` + + ``` + { + "public_key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5xd/p5oqJBpfZlU3WmdK\nfFS7ZwdUAssXyQDxXTUHe4LoZ3imFcpQed41Yu/rBxKQzLtSmHkpZ9Bw6bDplQrv\nuTYaeLtfiDUAa2kZVCBzDnFFx4J6v5jkoC4i1SYeGPtEW4D5m8xn8G5aCslEnpvL\nYQ1oXi14vORHzuC7uMfkDh+/JTOYkRw083lFjZEXEh5Jjf0mZNLN9PhBzOCzDalA\nBGhpcOATtkKxEDtXcyVNJEqf8sfpz7FKNpNBNIKb3EZX168OFp+yeK3pd1dhAc+F\nFkmZmwN+Qb065znGfdltxh9F75yPB1CeJEedirTVj/QvALSSkFlKS9TFgRgh7T2z\nKlj7Bw+fC9cXJCjUevgnl6pFvrEqVTu+topmWcEPKPJiI1xPVtFcRjEEgTnkTRcp\nfoDxKngh3oj1+5szBXwMKnnk1wc7TK8zqTcxEbLeSkiTxU5ptWasnkhqHoJyzO9w\njc7qasvSKxUou0+VD0W/EID4KkLgomkwiFUGFeYstpbpiC0FJS3M/JOLIibPRXK5\n4YMxw23bHqDP4J02J6NdmrLLiKXaRy2MCcxlovckswqYz/4xjT9ye9dc8DMengLn\n5+iDpxzCdBjvTdGXejY3gTvQ68JKz6TznBcz+ooh6K/bH950Kr3JDwZkCTpuZKQn\nXSWZhXVN1sHbZJn7IneyPKkCAwEAAQ==\n-----END PUBLIC KEY-----\n", + "certificate": "-----BEGIN CERTIFICATE-----\nMIIFTTCCAzWgAwIBAgIBADANBgkqhkiG9w0BAQ4FADBqMRMwEQYDVQQDDApFRkkg\nc2lnbmVyMQ4wDAYDVQQLDAVpbmZyYTESMBAGA1UECgwJb3BlbkV1bGVyMRAwDgYD\nVQQHDAdDaGVuZ2R1MRAwDgYDVQQIDAdTaWNodWFuMQswCQYDVQQGEwJDTjAeFw0y\nMzA1MDQwOTI0MDJaFw0yNDA1MTIwOTI0MDJaMGoxEzARBgNVBAMMCkVGSSBzaWdu\nZXIxDjAMBgNVBAsMBWluZnJhMRIwEAYDVQQKDAlvcGVuRXVsZXIxEDAOBgNVBAcM\nB0NoZW5nZHUxEDAOBgNVBAgMB1NpY2h1YW4xCzAJBgNVBAYTAkNOMIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5xd/p5oqJBpfZlU3WmdKfFS7ZwdUAssX\nyQDxXTUHe4LoZ3imFcpQed41Yu/rBxKQzLtSmHkpZ9Bw6bDplQrvuTYaeLtfiDUA\na2kZVCBzDnFFx4J6v5jkoC4i1SYeGPtEW4D5m8xn8G5aCslEnpvLYQ1oXi14vORH\nzuC7uMfkDh+/JTOYkRw083lFjZEXEh5Jjf0mZNLN9PhBzOCzDalABGhpcOATtkKx\nEDtXcyVNJEqf8sfpz7FKNpNBNIKb3EZX168OFp+yeK3pd1dhAc+FFkmZmwN+Qb06\n5znGfdltxh9F75yPB1CeJEedirTVj/QvALSSkFlKS9TFgRgh7T2zKlj7Bw+fC9cX\nJCjUevgnl6pFvrEqVTu+topmWcEPKPJiI1xPVtFcRjEEgTnkTRcpfoDxKngh3oj1\n+5szBXwMKnnk1wc7TK8zqTcxEbLeSkiTxU5ptWasnkhqHoJyzO9wjc7qasvSKxUo\nu0+VD0W/EID4KkLgomkwiFUGFeYstpbpiC0FJS3M/JOLIibPRXK54YMxw23bHqDP\n4J02J6NdmrLLiKXaRy2MCcxlovckswqYz/4xjT9ye9dc8DMengLn5+iDpxzCdBjv\nTdGXejY3gTvQ68JKz6TznBcz+ooh6K/bH950Kr3JDwZkCTpuZKQnXSWZhXVN1sHb\nZJn7IneyPKkCAwEAATANBgkqhkiG9w0BAQ4FAAOCAgEAObCqV91IlCpELDyDdVm1\nyc2xYlwbleeamI4lRQ9dbUxJgmoEvHrigTy6+QddTWTvq1ClB66FFr4CmP4R44ew\nOOnkUhdynZy23+qR0f9RKLpM/bQFzFAJJGkjVaz9OA0nD6lbGHxlljB0palnpeQN\nbXT42I9+pKQ+jmLQeUM5G2OYmEiOeATh5fDG50/Mi71vcjJBpcqoGy0eJQnbpTLr\nH3q3TjffpI4VmB4XZCdv4M8mTeZrT9fz40/tknUpGrD1ZDnOeAEX54KxCDhMpDPd\nJzhZAsd1zT23gEVyiJzXjnJb+ooCjLskFgIDRwim4/P8oMrmYJLC3PTf33AHiIsZ\nxka4Io7xNc58pAZPef1MLMRRxvZL2sHocZ1u3imPW0/9NdICLLCw+kCuXXZyjzsb\n3AhivrkA4pHuEakYcKZ7m4cbEdhn+A8VH+cZ6F8dOt683a3h/1KMUA9RgbmkRfOY\nBd0ifVYZNlL2P7+aRB5MYYdjvtFTjvuYnaiCsk0rfKeFcLRcdqH/LwsnmYI58ak+\noBg1q7IwUKiMMQJXv80sYpulMVNf4yogMwxuDb8aKSMoYYHqwpc/APxpxxIYqals\njm/mYiBzbODW1CkAXzFKlDxwbOHbYE/BjtQka4UKGoJbmhSRae9axKxj1bBj4Vud\ntTN6jZmbEb/Bmclsaooig1g=\n-----END CERTIFICATE-----\n" + } + ``` + - save the `certificate` filed as a PEM file + ``` + $ echo "-----BEGIN CERTIFICATE-----\nMIIFTTCCAzWgAwIBAgIBADANBgkqhkiG9w0BAQ4FADBqMRMwEQYDVQQDDApFRkkg\nc2lnbmVyMQ4wDAYDVQQLDAVpbmZyYTESMBAGA1UECgwJb3BlbkV1bGVyMRAwDgYD\nVQQHDAdDaGVuZ2R1MRAwDgYDVQQIDAdTaWNodWFuMQswCQYDVQQGEwJDTjAeFw0y\nMzA1MDQwOTI0MDJaFw0yNDA1MTIwOTI0MDJaMGoxEzARBgNVBAMMCkVGSSBzaWdu\nZXIxDjAMBgNVBAsMBWluZnJhMRIwEAYDVQQKDAlvcGVuRXVsZXIxEDAOBgNVBAcM\nB0NoZW5nZHUxEDAOBgNVBAgMB1NpY2h1YW4xCzAJBgNVBAYTAkNOMIICIjANBgkq\nhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5xd/p5oqJBpfZlU3WmdKfFS7ZwdUAssX\nyQDxXTUHe4LoZ3imFcpQed41Yu/rBxKQzLtSmHkpZ9Bw6bDplQrvuTYaeLtfiDUA\na2kZVCBzDnFFx4J6v5jkoC4i1SYeGPtEW4D5m8xn8G5aCslEnpvLYQ1oXi14vORH\nzuC7uMfkDh+/JTOYkRw083lFjZEXEh5Jjf0mZNLN9PhBzOCzDalABGhpcOATtkKx\nEDtXcyVNJEqf8sfpz7FKNpNBNIKb3EZX168OFp+yeK3pd1dhAc+FFkmZmwN+Qb06\n5znGfdltxh9F75yPB1CeJEedirTVj/QvALSSkFlKS9TFgRgh7T2zKlj7Bw+fC9cX\nJCjUevgnl6pFvrEqVTu+topmWcEPKPJiI1xPVtFcRjEEgTnkTRcpfoDxKngh3oj1\n+5szBXwMKnnk1wc7TK8zqTcxEbLeSkiTxU5ptWasnkhqHoJyzO9wjc7qasvSKxUo\nu0+VD0W/EID4KkLgomkwiFUGFeYstpbpiC0FJS3M/JOLIibPRXK54YMxw23bHqDP\n4J02J6NdmrLLiKXaRy2MCcxlovckswqYz/4xjT9ye9dc8DMengLn5+iDpxzCdBjv\nTdGXejY3gTvQ68JKz6TznBcz+ooh6K/bH950Kr3JDwZkCTpuZKQnXSWZhXVN1sHb\nZJn7IneyPKkCAwEAATANBgkqhkiG9w0BAQ4FAAOCAgEAObCqV91IlCpELDyDdVm1\nyc2xYlwbleeamI4lRQ9dbUxJgmoEvHrigTy6+QddTWTvq1ClB66FFr4CmP4R44ew\nOOnkUhdynZy23+qR0f9RKLpM/bQFzFAJJGkjVaz9OA0nD6lbGHxlljB0palnpeQN\nbXT42I9+pKQ+jmLQeUM5G2OYmEiOeATh5fDG50/Mi71vcjJBpcqoGy0eJQnbpTLr\nH3q3TjffpI4VmB4XZCdv4M8mTeZrT9fz40/tknUpGrD1ZDnOeAEX54KxCDhMpDPd\nJzhZAsd1zT23gEVyiJzXjnJb+ooCjLskFgIDRwim4/P8oMrmYJLC3PTf33AHiIsZ\nxka4Io7xNc58pAZPef1MLMRRxvZL2sHocZ1u3imPW0/9NdICLLCw+kCuXXZyjzsb\n3AhivrkA4pHuEakYcKZ7m4cbEdhn+A8VH+cZ6F8dOt683a3h/1KMUA9RgbmkRfOY\nBd0ifVYZNlL2P7+aRB5MYYdjvtFTjvuYnaiCsk0rfKeFcLRcdqH/LwsnmYI58ak+\noBg1q7IwUKiMMQJXv80sYpulMVNf4yogMwxuDb8aKSMoYYHqwpc/APxpxxIYqals\njm/mYiBzbODW1CkAXzFKlDxwbOHbYE/BjtQka4UKGoJbmhSRae9axKxj1bBj4Vud\ntTN6jZmbEb/Bmclsaooig1g=\n-----END CERTIFICATE-----\n" | sed -E 's|\\\n|\n|g' > certificate.pem + $ cat certificate + -----BEGIN CERTIFICATE----- + MIIFTTCCAzWgAwIBAgIBADANBgkqhkiG9w0BAQ4FADBqMRMwEQYDVQQDDApFRkkg + c2lnbmVyMQ4wDAYDVQQLDAVpbmZyYTESMBAGA1UECgwJb3BlbkV1bGVyMRAwDgYD + VQQHDAdDaGVuZ2R1MRAwDgYDVQQIDAdTaWNodWFuMQswCQYDVQQGEwJDTjAeFw0y + MzA1MDQwOTI0MDJaFw0yNDA1MTIwOTI0MDJaMGoxEzARBgNVBAMMCkVGSSBzaWdu + ZXIxDjAMBgNVBAsMBWluZnJhMRIwEAYDVQQKDAlvcGVuRXVsZXIxEDAOBgNVBAcM + B0NoZW5nZHUxEDAOBgNVBAgMB1NpY2h1YW4xCzAJBgNVBAYTAkNOMIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5xd/p5oqJBpfZlU3WmdKfFS7ZwdUAssX + yQDxXTUHe4LoZ3imFcpQed41Yu/rBxKQzLtSmHkpZ9Bw6bDplQrvuTYaeLtfiDUA + a2kZVCBzDnFFx4J6v5jkoC4i1SYeGPtEW4D5m8xn8G5aCslEnpvLYQ1oXi14vORH + zuC7uMfkDh+/JTOYkRw083lFjZEXEh5Jjf0mZNLN9PhBzOCzDalABGhpcOATtkKx + EDtXcyVNJEqf8sfpz7FKNpNBNIKb3EZX168OFp+yeK3pd1dhAc+FFkmZmwN+Qb06 + 5znGfdltxh9F75yPB1CeJEedirTVj/QvALSSkFlKS9TFgRgh7T2zKlj7Bw+fC9cX + JCjUevgnl6pFvrEqVTu+topmWcEPKPJiI1xPVtFcRjEEgTnkTRcpfoDxKngh3oj1 + +5szBXwMKnnk1wc7TK8zqTcxEbLeSkiTxU5ptWasnkhqHoJyzO9wjc7qasvSKxUo + u0+VD0W/EID4KkLgomkwiFUGFeYstpbpiC0FJS3M/JOLIibPRXK54YMxw23bHqDP + 4J02J6NdmrLLiKXaRy2MCcxlovckswqYz/4xjT9ye9dc8DMengLn5+iDpxzCdBjv + TdGXejY3gTvQ68JKz6TznBcz+ooh6K/bH950Kr3JDwZkCTpuZKQnXSWZhXVN1sHb + ZJn7IneyPKkCAwEAATANBgkqhkiG9w0BAQ4FAAOCAgEAObCqV91IlCpELDyDdVm1 + yc2xYlwbleeamI4lRQ9dbUxJgmoEvHrigTy6+QddTWTvq1ClB66FFr4CmP4R44ew + OOnkUhdynZy23+qR0f9RKLpM/bQFzFAJJGkjVaz9OA0nD6lbGHxlljB0palnpeQN + bXT42I9+pKQ+jmLQeUM5G2OYmEiOeATh5fDG50/Mi71vcjJBpcqoGy0eJQnbpTLr + H3q3TjffpI4VmB4XZCdv4M8mTeZrT9fz40/tknUpGrD1ZDnOeAEX54KxCDhMpDPd + JzhZAsd1zT23gEVyiJzXjnJb+ooCjLskFgIDRwim4/P8oMrmYJLC3PTf33AHiIsZ + xka4Io7xNc58pAZPef1MLMRRxvZL2sHocZ1u3imPW0/9NdICLLCw+kCuXXZyjzsb + 3AhivrkA4pHuEakYcKZ7m4cbEdhn+A8VH+cZ6F8dOt683a3h/1KMUA9RgbmkRfOY + Bd0ifVYZNlL2P7+aRB5MYYdjvtFTjvuYnaiCsk0rfKeFcLRcdqH/LwsnmYI58ak+ + oBg1q7IwUKiMMQJXv80sYpulMVNf4yogMwxuDb8aKSMoYYHqwpc/APxpxxIYqals + jm/mYiBzbODW1CkAXzFKlDxwbOHbYE/BjtQka4UKGoJbmhSRae9axKxj1bBj4Vud + tTN6jZmbEb/Bmclsaooig1g= + -----END CERTIFICATE----- + ``` + +# sign a EFI file +``` +RUST_BACKTRACE=1 RUST_LOG=debug ./target/debug/client -c client.toml add --file-type efi-image --key-type x509 --key-name my-x509 --sign-type authenticode `pwd`/shimx64.efi +``` + +# verify the EFI file +- first we should compile `sbsigntools` +``` +git clone https://git.kernel.org/pub/scm/linux/kernel/git/jejb/sbsigntools.git +cd sbsigntools +git submodule init && git submodule update +make +``` +- verify the signed EFI image using the certificate we exported +``` +$ src/sbverify `pwd`/shimx64.efi --cert certificate +warning: data remaining[827688 vs 953240]: gaps between PE/COFF sections? +Signature verification OK +``` \ No newline at end of file diff --git a/src/client/cmd/add.rs b/src/client/cmd/add.rs index 47b3f501ed28a6f9c73c7d2cd4f6cdf2dd576ac3..fa32b7552d93adf5af2182f32ce96c82f869da97 100644 --- a/src/client/cmd/add.rs +++ b/src/client/cmd/add.rs @@ -26,8 +26,8 @@ use std::collections::HashMap; use crate::util::error; use async_channel::{bounded}; - -use crate::client::cmd::options; +use crate::util::sign::{SignType, FileType, KeyType}; +use crate::util::options; use crate::client::file_handler::factory::FileHandlerFactory; use crate::client::load_balancer::factory::ChannelFactory; @@ -38,10 +38,11 @@ use crate::client::worker::traits::SignHandler; use std::sync::atomic::{AtomicI32, Ordering}; lazy_static! { - pub static ref FILE_EXTENSION: HashMap> = HashMap::from([ - (sign_identity::FileType::RPM, vec!["rpm", "srpm"]), - (sign_identity::FileType::CheckSum, vec!["txt", "sha256sum"]), - (sign_identity::FileType::KernelModule, vec!["ko"]), + pub static ref FILE_EXTENSION: HashMap> = HashMap::from([ + (FileType::RPM, vec!["rpm", "srpm"]), + (FileType::CheckSum, vec!["txt", "sha256sum"]), + (FileType::KernelModule, vec!["ko"]), + (FileType::EfiImage, vec!["efi"]), ]); } @@ -49,15 +50,15 @@ lazy_static! { pub struct CommandAdd { #[arg(long)] #[arg(value_enum)] - #[arg(help = "specify the file type for signing, currently support checksum and rpm")] - file_type: sign_identity::FileType, + #[arg(help = "specify the file type for signing")] + file_type: FileType, #[arg(long)] #[arg(value_enum)] - #[arg(help = "specify the key type for signing, currently support pgp and x509")] - key_type: sign_identity::KeyType, + #[arg(help = "specify the key type for signing")] + key_type: KeyType, #[arg(long)] - #[arg(help = "specify the key id for signing")] - key_id: String, + #[arg(help = "specify the key name for signing")] + key_name: String, #[arg(long)] #[arg(help = "create detached signature")] detached: bool, @@ -66,6 +67,10 @@ pub struct CommandAdd { skip_signed: bool, #[arg(help = "specify the path which will be used for signing file and directory are supported")] path: String, + #[arg(long)] + #[arg(value_enum, default_value_t=SignType::CMS)] + #[arg(help = "specify the signature type, meaningful when key type is x509")] + sign_type: SignType, } @@ -73,16 +78,17 @@ pub struct CommandAdd { pub struct CommandAddHandler { worker_threads: usize, working_dir: String, - file_type: sign_identity::FileType, - key_type: sign_identity::KeyType, - key_id: String, + file_type: FileType, + key_type: KeyType, + key_name: String, path: PathBuf, buffer_size: usize, signal: Arc, config: Arc>, detached: bool, skip_signed: bool, - max_concurrency: usize + max_concurrency: usize, + sign_type: SignType, } impl CommandAddHandler { @@ -91,7 +97,8 @@ impl CommandAddHandler { HashMap::from([ (options::DETACHED.to_string(), self.detached.to_string()), (options::SKIP_SIGNED.to_string(), self.skip_signed.to_string()), - (options::KEY_TYPE.to_string(), self.key_type.to_string())]) + (options::KEY_TYPE.to_string(), self.key_type.to_string()), + (options::SIGN_TYPE.to_string(), self.sign_type.to_string())]) } fn collect_file_candidates(&self) -> Result> { if self.path.is_dir() { @@ -109,7 +116,7 @@ impl CommandAddHandler { self.file_type.clone(), en.path().to_path_buf(), self.key_type.clone(), - self.key_id.clone(), + self.key_name.clone(), self.get_sign_options())); } } @@ -122,7 +129,7 @@ impl CommandAddHandler { return Ok(container); } else if self.file_candidates(self.path.extension().unwrap().to_str().unwrap())? { return Ok(vec![sign_identity::SignIdentity::new( - self.file_type.clone(), self.path.clone(), self.key_type.clone(), self.key_id.clone(), self.get_sign_options())]); + self.file_type.clone(), self.path.clone(), self.key_type.clone(), self.key_name.clone(), self.get_sign_options())]); } Err(error::Error::NoFileCandidateError) } @@ -153,13 +160,14 @@ impl SignCommand for CommandAddHandler { working_dir: config.read()?.get_string("working_dir")?, file_type: command.file_type, key_type: command.key_type, - key_id: command.key_id, + key_name: command.key_name, path: std::path::PathBuf::from(&command.path), signal, config: config.clone(), detached: command.detached, skip_signed: command.skip_signed, max_concurrency: config.read()?.get_string("max_concurrency")?.parse()?, + sign_type: command.sign_type, }) } diff --git a/src/client/cmd/mod.rs b/src/client/cmd/mod.rs index a3e13123cdba0b698574d4935598a05b81ef1f09..dbbd277b055159fc0de35847a8e4c866c743295a 100644 --- a/src/client/cmd/mod.rs +++ b/src/client/cmd/mod.rs @@ -1,3 +1,2 @@ pub mod add; -pub mod traits; -pub mod options; \ No newline at end of file +pub mod traits; \ No newline at end of file diff --git a/src/client/file_handler/checksum.rs b/src/client/file_handler/checksum.rs index fe91be2545139a1545fda35fdbcb484559bb3356..197b5d8eb062e74e2e75c9147a10a0b03508a252 100644 --- a/src/client/file_handler/checksum.rs +++ b/src/client/file_handler/checksum.rs @@ -14,53 +14,69 @@ * */ -use std::path::PathBuf; use super::traits::FileHandler; -use async_trait::async_trait; +use crate::util::sign::{SignType, KeyType}; use crate::util::error::Result; +use async_trait::async_trait; +use std::path::PathBuf; use tokio::fs; use uuid::Uuid; -use std::collections::HashMap; +use crate::util::options; use crate::util::error::Error; -use crate::client::cmd::options; - +use std::collections::HashMap; const FILE_EXTENSION: &str = "asc"; #[derive(Clone)] -pub struct CheckSumFileHandler { - -} +pub struct CheckSumFileHandler {} impl CheckSumFileHandler { pub fn new() -> Self { - Self { - - } + Self {} } } #[async_trait] impl FileHandler for CheckSumFileHandler { - fn validate_options(&self, sign_options: &HashMap) -> Result<()> { if let Some(detached) = sign_options.get(options::DETACHED) { if detached == "false" { - return Err(Error::InvalidArgumentError("checksum file only support detached signature".to_string())) + return Err(Error::InvalidArgumentError( + "checksum file only support detached signature".to_string(), + )); + } + } + + if let Some(key_type) = sign_options.get(options::KEY_TYPE) { + if let Some(sign_type) = sign_options.get(options::SIGN_TYPE) { + if sign_type != SignType::CMS.to_string().as_str() + && key_type == KeyType::X509.to_string().as_str() + { + return Err(Error::InvalidArgumentError( + "checksum file only support x509 key with cms sign type".to_string(), + )); + } } } Ok(()) } /* when assemble checksum signature when only create another .asc file separately */ - async fn assemble_data(&self, path: &PathBuf, data: Vec>, temp_dir: &PathBuf, _sign_options: &HashMap) -> Result<(String, String)> { + async fn assemble_data( + &self, + path: &PathBuf, + data: Vec>, + temp_dir: &PathBuf, + _sign_options: &HashMap, + ) -> Result<(String, String)> { let temp_file = temp_dir.join(Uuid::new_v4().to_string()); //convert bytes into string let result = String::from_utf8_lossy(&data[0]); fs::write(temp_file.clone(), result.as_bytes()).await?; - Ok((temp_file.as_path().display().to_string(), - format!("{}.{}", path.as_path().display(), FILE_EXTENSION))) + Ok(( + temp_file.as_path().display().to_string(), + format!("{}.{}", path.as_path().display(), FILE_EXTENSION), + )) } } - diff --git a/src/client/file_handler/efi.rs b/src/client/file_handler/efi.rs new file mode 100644 index 0000000000000000000000000000000000000000..933710509fe9ed39252193e6ba7aa78b0c80d164 --- /dev/null +++ b/src/client/file_handler/efi.rs @@ -0,0 +1,91 @@ +use super::traits::FileHandler; +use crate::util::options; +use crate::util::sign::{SignType, KeyType}; +use crate::util::error::{Error, Result}; +use async_trait::async_trait; +use efi_signer::{DigestAlgorithm, EfiImage}; +use std::collections::HashMap; +use std::fs::read; +use std::path::PathBuf; +use uuid::Uuid; +use std::io::Write; +pub struct EfiFileHandler {} + +impl EfiFileHandler { + pub fn new() -> Self { + Self {} + } +} + +#[async_trait] +impl FileHandler for EfiFileHandler { + fn validate_options(&self, sign_options: &HashMap) -> Result<()> { + if let Some(detach) = sign_options.get(options::DETACHED) { + if detach == "true" { + return Err(Error::InvalidArgumentError( + "EFI image not support detached signature, you may need remove the --detach argument".to_string(), + )); + } + } + + if let Some(key_type) = sign_options.get(options::KEY_TYPE) { + if key_type != KeyType::X509.to_string().as_str() { + return Err(Error::InvalidArgumentError( + "EFI image only support x509 key type".to_string(), + )); + } + } + + if let Some(sign_type) = sign_options.get(options::SIGN_TYPE) { + if sign_type != SignType::Authenticode.to_string().as_str() { + return Err(Error::InvalidArgumentError( + "EFI image only support authenticode sign type".to_string(), + )); + } + } + Ok(()) + } + + async fn split_data( + &self, + path: &PathBuf, + _sign_options: &mut HashMap, + ) -> Result>> { + let buf = read(path)?; + let pe = EfiImage::parse(&buf)?; + let digest = match pe.get_digest_algo()? { + Some(algo) => pe.compute_digest(algo)?, + None => pe.compute_digest(DigestAlgorithm::Sha256)?, + }; + info!("file {} digest {:x?}", path.as_path().display().to_string(), digest.as_slice()); + Ok(vec![digest]) + } + + async fn assemble_data( + &self, + path: &PathBuf, + data: Vec>, + temp_dir: &PathBuf, + _sign_options: &HashMap, + ) -> Result<(String, String)> { + let temp_file = temp_dir.join(Uuid::new_v4().to_string()); + let buf = read(path)?; + let pe = EfiImage::parse(&buf)?; + + let mut signatures :Vec = Vec::new(); + + for d in data.iter() { + signatures.push(efi_signer::Signature::decode(&d)?); + } + let new_pe = pe.set_authenticode(signatures)?; + + let mut file = std::fs::File::create(&temp_file)?; + + file.write_all(&new_pe)?; + + Ok(( + temp_file.as_path().display().to_string(), + path.display().to_string(), + )) + } +} diff --git a/src/client/file_handler/factory.rs b/src/client/file_handler/factory.rs index 732a63dcba1c9e3553fe440291a467675b6363e3..561273be1d135e3e97046471bd6c54f2782320ac 100644 --- a/src/client/file_handler/factory.rs +++ b/src/client/file_handler/factory.rs @@ -15,9 +15,10 @@ */ use super::rpm::RpmFileHandler; +use super::efi::EfiFileHandler; use super::checksum::CheckSumFileHandler; use super::kernel_module::KernelModuleFileHandler; -use crate::client::sign_identity::FileType; +use crate::util::sign::FileType; use super::traits::FileHandler; pub struct FileHandlerFactory { @@ -34,7 +35,10 @@ impl FileHandlerFactory { }, FileType::KernelModule => { Box::new(KernelModuleFileHandler::new()) - } + }, + FileType::EfiImage => { + Box::new(EfiFileHandler::new()) + }, } } } \ No newline at end of file diff --git a/src/client/file_handler/kernel_module.rs b/src/client/file_handler/kernel_module.rs index 4c2b34ee86eb0c3255cc68fb6e9f6543657098b8..27c318992ac07a3ee7af242d3e3266f0aa4b72f4 100644 --- a/src/client/file_handler/kernel_module.rs +++ b/src/client/file_handler/kernel_module.rs @@ -14,25 +14,24 @@ * */ -use std::path::PathBuf; -use std::str; use super::traits::FileHandler; -use async_trait::async_trait; use crate::util::error::Result; +use async_trait::async_trait; use std::fs; use std::io; +use std::path::PathBuf; +use std::str; -use uuid::Uuid; -use std::io::{Read, Seek, Write}; use bincode::{config, Decode, Encode}; use std::collections::HashMap; +use std::io::{Read, Seek, Write}; use std::os::raw::{c_uchar, c_uint}; +use uuid::Uuid; -use crate::client::cmd::options; -use crate::client::sign_identity::KeyType; +use crate::util::options; +use crate::util::sign::{SignType, KeyType}; use crate::util::error::Error; - const FILE_EXTENSION: &str = "p7s"; const PKEY_ID_PKCS7: c_uchar = 2; const MAGIC_NUMBER: &str = "~Module signature appended~\n"; @@ -66,9 +65,7 @@ impl ModuleSignature { } #[derive(Clone)] -pub struct KernelModuleFileHandler { - -} +pub struct KernelModuleFileHandler {} impl KernelModuleFileHandler { pub fn new() -> Self { @@ -81,7 +78,12 @@ impl KernelModuleFileHandler { Ok(()) } - pub fn append_inline_signature(&self, module: &PathBuf, tempfile: &PathBuf, signature: &[u8]) -> Result<()> { + pub fn append_inline_signature( + &self, + module: &PathBuf, + tempfile: &PathBuf, + signature: &[u8], + ) -> Result<()> { let mut signed = fs::File::create(tempfile)?; signed.write_all(&self.get_raw_content(module)?)?; signed.write_all(signature)?; @@ -100,7 +102,7 @@ impl KernelModuleFileHandler { let raw_content = fs::read(path)?; let mut file = fs::File::open(path)?; if file.metadata()?.len() <= MAGIC_NUMBER_SIZE as u64 { - return Ok(raw_content) + return Ok(raw_content); } //identify magic string and end of the file file.seek(io::SeekFrom::End(-(MAGIC_NUMBER_SIZE as i64)))?; @@ -118,56 +120,82 @@ impl KernelModuleFileHandler { config::standard() .with_fixed_int_encoding() .with_big_endian(), - )?.0; + )? + .0; if raw_content.len() < SIGNATURE_SIZE + signature.sig_len as usize { - return Err(Error::SplitFileError("invalid kernel module signature size found".to_owned())); + return Err(Error::SplitFileError( + "invalid kernel module signature size found".to_owned(), + )); } //read raw content - Ok(raw_content[0..(raw_content.len() - SIGNATURE_SIZE - signature.sig_len as usize)].to_owned()) + Ok(raw_content + [0..(raw_content.len() - SIGNATURE_SIZE - signature.sig_len as usize)] + .to_owned()) } else { Ok(raw_content) - } + }; } Err(_) => { //try to read whole content Ok(raw_content) } - } + }; } } #[async_trait] impl FileHandler for KernelModuleFileHandler { - fn validate_options(&self, sign_options: &HashMap) -> Result<()> { if let Some(key_type) = sign_options.get(options::KEY_TYPE) { if key_type != KeyType::X509.to_string().as_str() { - return Err(Error::InvalidArgumentError("kernel module file only support x509 signature".to_string())) + return Err(Error::InvalidArgumentError( + "kernel module file only support x509 signature".to_string(), + )); + } + } + + if let Some(sign_type) = sign_options.get(options::SIGN_TYPE) { + if sign_type != SignType::CMS.to_string().as_str() { + return Err(Error::InvalidArgumentError( + "kernel module file only support cms sign type".to_string(), + )); } } Ok(()) } //NOTE: currently we don't support sign signed kernel module file - async fn split_data(&self, path: &PathBuf, _sign_options: &mut HashMap) -> Result>> { + async fn split_data( + &self, + path: &PathBuf, + _sign_options: &mut HashMap, + ) -> Result>> { Ok(vec![self.get_raw_content(path)?]) } /* when assemble checksum signature when only create another .asc file separately */ - async fn assemble_data(&self, path: &PathBuf, data: Vec>, temp_dir: &PathBuf, sign_options: &HashMap) -> Result<(String, String)> { + async fn assemble_data( + &self, + path: &PathBuf, + data: Vec>, + temp_dir: &PathBuf, + sign_options: &HashMap, + ) -> Result<(String, String)> { let temp_file = temp_dir.join(Uuid::new_v4().to_string()); //convert bytes into string if let Some(detached) = sign_options.get("detached") { if detached == "true" { self.generate_detached_signature(&temp_file.display().to_string(), &data[0])?; - return Ok((temp_file.as_path().display().to_string(), - format!("{}.{}", path.display(), FILE_EXTENSION))) + return Ok(( + temp_file.as_path().display().to_string(), + format!("{}.{}", path.display(), FILE_EXTENSION), + )); } } self.append_inline_signature(path, &temp_file, &data[0])?; - return Ok((temp_file.as_path().display().to_string(), - path.display().to_string())) - + return Ok(( + temp_file.as_path().display().to_string(), + path.display().to_string(), + )); } } - diff --git a/src/client/file_handler/mod.rs b/src/client/file_handler/mod.rs index c113a46b7d52871fe98edfaa6bf609e8b13d82cd..32619f669620df1f4ca7fdd35f73c6692728fd34 100644 --- a/src/client/file_handler/mod.rs +++ b/src/client/file_handler/mod.rs @@ -1,4 +1,5 @@ pub mod rpm; +pub mod efi; pub mod traits; pub mod factory; pub mod checksum; diff --git a/src/client/file_handler/rpm.rs b/src/client/file_handler/rpm.rs index 9b296fa0934f52beafae1a098ea439b12576c595..c6bed11ce85239d4e644f790b8dac8535048fd11 100644 --- a/src/client/file_handler/rpm.rs +++ b/src/client/file_handler/rpm.rs @@ -26,8 +26,8 @@ use rpm::{Header, IndexSignatureTag, RPMPackage}; use super::sequential_cursor::SeqCursor; use uuid::Uuid; use sha1; -use crate::client::cmd::options; -use crate::client::sign_identity::KeyType; +use crate::util::options; +use crate::util::sign::KeyType; use crate::util::error::Error; #[derive(Clone)] diff --git a/src/client/file_handler/traits.rs b/src/client/file_handler/traits.rs index ffb2cb064fb148cf6e63fe22ca8550fb16fcce17..1767002f693c2eea1a2318acced6f0ee65aac0ce 100644 --- a/src/client/file_handler/traits.rs +++ b/src/client/file_handler/traits.rs @@ -14,22 +14,29 @@ * */ -use std::collections::HashMap; +use crate::util::error::Result; use async_trait::async_trait; +use std::collections::HashMap; use std::path::PathBuf; -use crate::util::error::Result; use tokio::fs; #[async_trait] pub trait FileHandler: Send + Sync { - - fn validate_options(&self, _sign_options: &HashMap) -> Result<()> { - Ok(()) - } - async fn split_data(&self, path: &PathBuf, _sign_options: &mut HashMap) -> Result>> { + fn validate_options(&self, _sign_options: &HashMap) -> Result<()>; + async fn split_data( + &self, + path: &PathBuf, + _sign_options: &mut HashMap, + ) -> Result>> { let content = fs::read(path).await?; Ok(vec![content]) } //return the temporary file path and signature file name - async fn assemble_data(&self, path: &PathBuf, data: Vec>, temp_dir: &PathBuf, sign_options: &HashMap) -> Result<(String, String)>; -} \ No newline at end of file + async fn assemble_data( + &self, + path: &PathBuf, + data: Vec>, + temp_dir: &PathBuf, + sign_options: &HashMap, + ) -> Result<(String, String)>; +} diff --git a/src/client/load_balancer/factory.rs b/src/client/load_balancer/factory.rs index 923e594a35ab9c8703bbf84741ad4079469f4416..e527df92e960bc2d555633e06aeae2fd748196bb 100644 --- a/src/client/load_balancer/factory.rs +++ b/src/client/load_balancer/factory.rs @@ -29,14 +29,15 @@ pub struct ChannelFactory { impl ChannelFactory { pub async fn new(config: &HashMap) -> Result { let mut client_config :Option = None; - let tls_cert = config.get("tls_cert").unwrap_or(&Value::default()).to_string(); - let tls_key = config.get("tls_key").unwrap_or(&Value::default()).to_string(); - let server_port = config.get("server_port").unwrap_or(&Value::default()).to_string(); + let tls_cert = config.get("tls_cert").unwrap_or(&Value::new(Some(&String::new()), config::ValueKind::String(String::new()))).to_string(); + let tls_key = config.get("tls_key").unwrap_or(&Value::new(Some(&String::new()), config::ValueKind::String(String::new()))).to_string(); + let server_port = config.get("server_port").expect("server port not in client config").to_string(); if tls_cert.is_empty() || tls_key.is_empty() { info!("tls client key and cert not configured, tls will be disabled"); } else { info!("tls client key and cert configured, tls will be enabled"); + debug!("tls cert:{}, tls key:{}", tls_cert, tls_key); let identity = Identity::from_pem( tokio::fs::read(tls_cert).await?, tokio::fs::read(tls_key).await?); diff --git a/src/client/sign_identity.rs b/src/client/sign_identity.rs index 5fce614349fcaa2fe212abb805c3a92a286d7293..6002ae4cefdabbfbedbe6f16418858d33ee64f5d 100644 --- a/src/client/sign_identity.rs +++ b/src/client/sign_identity.rs @@ -15,43 +15,12 @@ */ use std::path::PathBuf; -use std::fmt::{Display, Formatter, Result as fmtResult}; use std::cell::{RefCell}; -use crate::util::error::Result; +use crate::util::error::{Result}; +use crate::util::sign::{FileType, KeyType}; use std::collections::HashMap; -#[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq, Hash)] -pub enum FileType { - RPM, - CheckSum, - KernelModule -} - -impl Display for FileType { - fn fmt(&self, f: &mut Formatter) -> fmtResult { - match self { - FileType::RPM => write!(f, "rpm"), - FileType::CheckSum => write!(f, "checksum"), - FileType::KernelModule => write!(f, "ko") - } - } -} - -#[derive(clap::ValueEnum, Clone, Debug, PartialEq)] -pub enum KeyType { - PGP, - X509, -} - -impl Display for KeyType { - fn fmt(&self, f: &mut Formatter) -> fmtResult { - match self { - KeyType::PGP => write!(f, "pgp"), - KeyType::X509 => write!(f, "x509"), - } - } -} pub struct SignIdentity { //absolute file path diff --git a/src/client/worker/signer.rs b/src/client/worker/signer.rs index 105e96354e1aa582ba71c29e905b30ad9ac58183..815e8fee394c8076dfb38d40966f4f713ee770a3 100644 --- a/src/client/worker/signer.rs +++ b/src/client/worker/signer.rs @@ -24,7 +24,7 @@ pub mod signatrust { } use tonic::transport::Channel; -use signatrust::{ +use self::signatrust::{ signatrust_client::SignatrustClient, SignStreamRequest, }; diff --git a/src/control_admin_entrypoint.rs b/src/control_admin_entrypoint.rs index 1e2b7e1443046d9e18cbe173c7f2210366d3ca6b..61824a89b90285d095cc8d02b23f18c65905d295 100644 --- a/src/control_admin_entrypoint.rs +++ b/src/control_admin_entrypoint.rs @@ -20,9 +20,9 @@ use std::collections::HashMap; use std::env; use chrono::{Duration, Utc}; use crate::util::error::{Result}; +use crate::util::sign::KeyType; use clap::{Parser, Subcommand}; use clap::{Args}; -use crate::client::sign_identity; use crate::domain::datakey::entity::{DataKey}; use crate::domain::user::entity::User; use crate::presentation::handler::control::model::datakey::dto::{CreateDataKeyDTO}; @@ -120,7 +120,7 @@ pub struct CommandGenerateKeys { #[arg(long)] #[arg(value_enum)] #[arg(help = "specify th type of key")] - key_type: sign_identity::KeyType, + key_type: KeyType, } fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap { @@ -128,10 +128,10 @@ fn generate_keys_parameters(command: &CommandGenerateKeys) -> HashMap, _options: HashMap) -> Result> { let private_key = PKey::private_key_from_pem(self.private_key.unsecure())?; let certificate = x509::X509::from_pem(self.certificate.unsecure())?; + if let Some(sign_type) = _options.get(options::SIGN_TYPE) { + if sign_type == SignType::Authenticode.to_string().as_str() { + // convert pem to p7b format + let p7b = efi_signer::EfiImage::pem_to_p7(self.certificate.unsecure())?; + return Ok(efi_signer::EfiImage::do_sign_signature(content, p7b, private_key.private_key_to_pem_pkcs8()?, None)?.encode()?); + } + } + //cms option reference: https://man.openbsd.org/CMS_sign.3 let cms_signature = CmsContentInfo::sign( Some(&certificate), @@ -216,6 +226,7 @@ impl SignPlugins for X509Plugin { | CMSOptions::NOSMIMECAP | CMSOptions::NOATTR, )?; + Ok(cms_signature.to_der()?) } } diff --git a/src/presentation/handler/data/sign_handler.rs b/src/presentation/handler/data/sign_handler.rs index e02a9a305e3cfca5c5ff15cb3d7dcc2e7c677c54..d543c4ce03e37451b6059d77eaec2a8284783dae 100644 --- a/src/presentation/handler/data/sign_handler.rs +++ b/src/presentation/handler/data/sign_handler.rs @@ -67,6 +67,7 @@ where key_type = inner_result.key_type; options = inner_result.options; } + debug!("begin to sign key_type :{} key_name: {}", key_type, key_name); match self.key_service.sign(key_type, key_name, &options, data).await { Ok(content) => { Ok(Response::new(SignStreamResponse { diff --git a/src/util/error.rs b/src/util/error.rs index 04c7b83ce251668a63c011158aea2e9205635bb3..672ac825b73ea5012c3f6e4178a37453164782ab 100644 --- a/src/util/error.rs +++ b/src/util/error.rs @@ -41,6 +41,7 @@ use openidconnect::ConfigurationError; use openidconnect::UserInfoError; use anyhow::Error as AnyhowError; use utoipa::{ToSchema}; +use efi_signer::error::Error as EFIError; pub type Result = std::result::Result; @@ -113,6 +114,8 @@ pub enum Error { BincodeError(String), #[error("failed to sign some of the files")] PartialSuccessError, + #[error("Error in sign or parse EFI image")] + EFIError(String), } #[derive(Deserialize, Serialize, ToSchema)] @@ -332,4 +335,9 @@ impl From>> for Erro } +impl From for Error { + fn from(error: EFIError) -> Self { + Error::EFIError(error.to_string()) + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 6d0790b33fce6a6421d400d1b37c06833c931779..c7ae5f56aba0fd00c14ca4010464861fcc7f6bb1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,6 @@ pub mod config; pub mod error; pub mod key; -pub mod signer_container; \ No newline at end of file +pub mod signer_container; +pub mod options; +pub mod sign; \ No newline at end of file diff --git a/src/client/cmd/options.rs b/src/util/options.rs similarity index 90% rename from src/client/cmd/options.rs rename to src/util/options.rs index 647573d2e77afa6f9f458c9a0451ed009449a9d2..6eefb04ab77a418cbfd43e6fee612e09fe7ad758 100644 --- a/src/client/cmd/options.rs +++ b/src/util/options.rs @@ -16,4 +16,5 @@ pub const DETACHED: &str = "detached"; pub const SKIP_SIGNED: &str = "skip_signed"; -pub const KEY_TYPE: &str = "key_type"; \ No newline at end of file +pub const KEY_TYPE: &str = "key_type"; +pub const SIGN_TYPE: &str = "sign_type"; \ No newline at end of file diff --git a/src/util/sign.rs b/src/util/sign.rs new file mode 100644 index 0000000000000000000000000000000000000000..7eb67657b5b362cd7c32b1d5ea83eca540e2c1b5 --- /dev/null +++ b/src/util/sign.rs @@ -0,0 +1,64 @@ +use super::error::{Error, Result}; +use std::fmt::{Display, Formatter, Result as fmtResult}; +use std::str::FromStr; + +#[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq, Hash)] +pub enum SignType { + CMS, // signed method for a CMS signed data + Authenticode, // signed method for signing EFI image using authenticode spec +} + +impl Display for SignType { + fn fmt(&self, f: &mut Formatter) -> fmtResult { + match self { + SignType::CMS => write!(f, "cms"), + SignType::Authenticode => write!(f, "authenticode"), + } + } +} + +impl FromStr for SignType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "cms" => Ok(SignType::CMS), + "authenticode" => Ok(SignType::Authenticode), + _ => Err(Error::ParameterError("Invalid sign_type param".to_string())), + } + } +} + +#[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq, Hash)] +pub enum FileType { + RPM, + CheckSum, + KernelModule, + EfiImage, +} + +impl Display for FileType { + fn fmt(&self, f: &mut Formatter) -> fmtResult { + match self { + FileType::RPM => write!(f, "rpm"), + FileType::CheckSum => write!(f, "checksum"), + FileType::KernelModule => write!(f, "ko"), + FileType::EfiImage => write!(f, "efi"), + } + } +} + +#[derive(clap::ValueEnum, Clone, Debug, PartialEq)] +pub enum KeyType { + PGP, + X509, +} + +impl Display for KeyType { + fn fmt(&self, f: &mut Formatter) -> fmtResult { + match self { + KeyType::PGP => write!(f, "pgp"), + KeyType::X509 => write!(f, "x509"), + } + } +}