1 //! This module describes a public key that is restricted to one of the supported algorithms.
2
3 use anyhow::{ensure, Context, Result};
4 use openssl::hash::MessageDigest;
5 use openssl::nid::Nid;
6 use openssl::pkey::{HasParams, Id, PKey, PKeyRef, Public};
7 use openssl::sign::Verifier;
8 use std::error::Error;
9 use std::fmt;
10
11 /// Enumeration of the kinds of key that are supported.
12 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
13 pub(crate) enum Kind {
14 Ed25519,
15 Ec(EcKind),
16 }
17
18 /// Enumeration of the kinds of elliptic curve keys that are supported.
19 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
20 pub(crate) enum EcKind {
21 P256,
22 P384,
23 }
24
25 /// Struct wrapping the public key and relevant validation methods.
26 #[derive(Clone, Debug)]
27 pub struct PublicKey {
28 kind: Kind,
29 pkey: PKey<Public>,
30 }
31
32 impl PublicKey {
kind(&self) -> Kind33 pub(crate) fn kind(&self) -> Kind {
34 self.kind
35 }
36
pkey(&self) -> &PKeyRef<Public>37 pub(crate) fn pkey(&self) -> &PKeyRef<Public> {
38 &self.pkey
39 }
40
41 /// Verify that the signature obtained from signing the given message
42 /// with the PublicKey matches the signature provided.
verify(&self, signature: &[u8], message: &[u8]) -> Result<()>43 pub fn verify(&self, signature: &[u8], message: &[u8]) -> Result<()> {
44 let mut verifier = match self.kind {
45 Kind::Ed25519 => Verifier::new_without_digest(&self.pkey),
46 Kind::Ec(ec) => Verifier::new(digest_for_ec(ec), &self.pkey),
47 }
48 .with_context(|| format!("Failed to create verifier {:?}", self.kind))?;
49 let verified =
50 verifier.verify_oneshot(signature, message).context("Failed to verify signature")?;
51 ensure!(verified, "Signature verification failed.");
52 Ok(())
53 }
54
55 /// Serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure.
to_pem(&self) -> String56 pub fn to_pem(&self) -> String {
57 String::from_utf8(self.pkey.public_key_to_pem().unwrap()).unwrap()
58 }
59 }
60
61 /// The error type returned when converting from [`PKey'] to [`PublicKey`] fails.
62 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
63 pub struct TryFromPKeyError(());
64
65 impl fmt::Display for TryFromPKeyError {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result66 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(fmt, "unsupported public key conversion attempted")
68 }
69 }
70
71 impl Error for TryFromPKeyError {}
72
73 impl TryFrom<PKey<Public>> for PublicKey {
74 type Error = TryFromPKeyError;
75
try_from(pkey: PKey<Public>) -> Result<Self, Self::Error>76 fn try_from(pkey: PKey<Public>) -> Result<Self, Self::Error> {
77 let kind = pkey_kind(&pkey).ok_or(TryFromPKeyError(()))?;
78 Ok(Self { kind, pkey })
79 }
80 }
81
pkey_kind<T: HasParams>(pkey: &PKeyRef<T>) -> Option<Kind>82 fn pkey_kind<T: HasParams>(pkey: &PKeyRef<T>) -> Option<Kind> {
83 match pkey.id() {
84 Id::ED25519 => Some(Kind::Ed25519),
85 Id::EC => match pkey.ec_key().unwrap().group().curve_name() {
86 Some(Nid::X9_62_PRIME256V1) => Some(Kind::Ec(EcKind::P256)),
87 Some(Nid::SECP384R1) => Some(Kind::Ec(EcKind::P384)),
88 _ => None,
89 },
90 _ => None,
91 }
92 }
93
digest_for_ec(ec: EcKind) -> MessageDigest94 fn digest_for_ec(ec: EcKind) -> MessageDigest {
95 match ec {
96 EcKind::P256 => MessageDigest::sha256(),
97 EcKind::P384 => MessageDigest::sha384(),
98 }
99 }
100
101 #[cfg(test)]
102 mod tests {
103 use super::*;
104
105 #[test]
from_ed25519_pkey()106 fn from_ed25519_pkey() {
107 let pkey = load_public_pkey(testkeys::ED25519_KEY_PEM[0]);
108 let key: PublicKey = pkey.clone().try_into().unwrap();
109 assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
110 }
111
112 #[test]
from_p256_pkey()113 fn from_p256_pkey() {
114 let pkey = load_public_pkey(testkeys::P256_KEY_PEM[0]);
115 let key: PublicKey = pkey.clone().try_into().unwrap();
116 assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
117 }
118
119 #[test]
from_p384_pkey()120 fn from_p384_pkey() {
121 let pkey = load_public_pkey(testkeys::P384_KEY_PEM[0]);
122 let key: PublicKey = pkey.clone().try_into().unwrap();
123 assert_eq!(key.to_pem().as_bytes(), pkey.public_key_to_pem().unwrap());
124 }
125
126 #[test]
from_p521_pkey_not_supported()127 fn from_p521_pkey_not_supported() {
128 let pkey = load_public_pkey(testkeys::P521_KEY_PEM[0]);
129 assert!(PublicKey::try_from(pkey).is_err());
130 }
131
132 #[test]
from_rsa2048_pkey_not_supported()133 fn from_rsa2048_pkey_not_supported() {
134 let pkey = load_public_pkey(testkeys::RSA2048_KEY_PEM[0]);
135 assert!(PublicKey::try_from(pkey).is_err());
136 }
137
load_public_pkey(pem: &str) -> PKey<Public>138 pub fn load_public_pkey(pem: &str) -> PKey<Public> {
139 testkeys::public_from_private(&PKey::private_key_from_pem(pem.as_bytes()).unwrap())
140 }
141 }
142
143 /// Keys and key handling utilities for use in tests.
144 #[cfg(test)]
145 pub(crate) mod testkeys {
146 use super::*;
147 use openssl::pkey::Private;
148 use openssl::sign::Signer;
149
150 pub struct PrivateKey {
151 kind: Kind,
152 pkey: PKey<Private>,
153 }
154
155 impl PrivateKey {
from_pem(pem: &str) -> Self156 pub fn from_pem(pem: &str) -> Self {
157 let pkey = PKey::private_key_from_pem(pem.as_bytes()).unwrap();
158 let kind = pkey_kind(&pkey).expect("unsupported private key");
159 Self { kind, pkey }
160 }
161
kind(&self) -> Kind162 pub(crate) fn kind(&self) -> Kind {
163 self.kind
164 }
165
public_key(&self) -> PublicKey166 pub fn public_key(&self) -> PublicKey {
167 public_from_private(&self.pkey).try_into().unwrap()
168 }
169
sign(&self, message: &[u8]) -> Result<Vec<u8>>170 pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>> {
171 let mut signer = match self.kind {
172 Kind::Ed25519 => Signer::new_without_digest(&self.pkey)?,
173 Kind::Ec(ec) => Signer::new(digest_for_ec(ec), &self.pkey)?,
174 };
175 signer.sign_oneshot_to_vec(message).context("signing message")
176 }
177 }
178
179 /// Gives the public key that matches the private key.
public_from_private(pkey: &PKey<Private>) -> PKey<Public>180 pub fn public_from_private(pkey: &PKey<Private>) -> PKey<Public> {
181 // It feels like there should be a more direct way to do this but I haven't found it.
182 PKey::public_key_from_der(&pkey.public_key_to_der().unwrap()).unwrap()
183 }
184
185 /// A selection of Ed25519 private keys.
186 pub const ED25519_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
187 MC4CAQAwBQYDK2VwBCIEILKW0KEeuieFxhDAzigQPE4XRTiQx+0/AlAjJqHmUWE6\n\
188 -----END PRIVATE KEY-----\n"];
189
190 /// A selection of elliptic curve P-256 private keys.
191 pub const P256_KEY_PEM: &[&str] = &[
192 "-----BEGIN PRIVATE KEY-----\n\
193 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+CO3ZBuAsimwPKAL\n\
194 IeDyCh4cRZ5EMd6llGu5MQCpibGhRANCAAQObPxc4bIPjupILrvKJjTrpTcyCf6q\n\
195 V552FlS67fGphwhg2LDfQ8adEdkuRfQvk+IvKJz8MDcPjErBG3Wlps1N\n\
196 -----END PRIVATE KEY-----\n",
197 "-----BEGIN PRIVATE KEY-----\n\
198 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgw1OPIcfQv5twO68B\n\
199 H+xNstW3DLXC6e4PGEYG/VppYVahRANCAAQMyWyv4ffVMu+wVNhNEk2mQSaTmSl/\n\
200 dLdRbEowfqPwMzdqdQ3QlKSV4ZcU2lsJEuQMkZzmVPz02enY2qcKctmj\n\
201 -----END PRIVATE KEY-----\n",
202 "-----BEGIN PRIVATE KEY-----\n\
203 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgbXO6ee7i7sY4YfFS\n\
204 Gn60ScPuL3QuYFMX4nJbcqPSQ7+hRANCAAS8i9xA8cIcWStbMG97YrttQsYEIR2a\n\
205 15+alxbb6b7422FuxBB0qG5nJ4m+Jd3Bp+N2lwx4rHBFDqU4cp8VlQav\n\
206 -----END PRIVATE KEY-----\n",
207 "-----BEGIN PRIVATE KEY-----\n\
208 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg/JuxkbpPyyouat11\n\
209 szDR+OA7d/fuMk9IhGkH7z1xHzChRANCAASRlY0D7Uh5T/FmB6txGr21w6jqKW2x\n\
210 RXdsaZgCB6XnrXlkgkvuWDc0CTLSBWdPFgW6OX0fyXViglEBH95REyQr\n\
211 -----END PRIVATE KEY-----\n",
212 ];
213
214 /// A selection of EC keys that should have leading zeros in their coordinates
215 pub const P256_KEY_WITH_LEADING_ZEROS_PEM: &[&str] = &[
216 // 31 byte Y coordinate:
217 "-----BEGIN PRIVATE KEY-----\n\
218 MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCWbRSB3imI03F5YNVq\n\
219 8AN8ZbyzW/h+5BQ53caD5VkWJg==\n\
220 -----END PRIVATE KEY-----\n",
221 // 31 byte X coordinate:
222 "-----BEGIN PRIVATE KEY-----\n\
223 MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCDe5E5WqNmCLxtsCNTc\n\
224 UOb9CPXCn6l3CZpbrp0aivb+Bw==\n\
225 -----END PRIVATE KEY-----\n",
226 // X & Y both have MSB set, and some stacks will add a padding byte
227 "-----BEGIN PRIVATE KEY-----\n\
228 MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCWOWcXPDEVZ4Qz3EBK\n\
229 uvSqhD9HmxDGxcNe3yxKi9pazw==\n\
230 -----END PRIVATE KEY-----\n",
231 ];
232
233 /// A selection of elliptic curve P-384 private keys.
234 pub const P384_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
235 MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBMZ414LiUpcuNTNq5W\n\
236 Ig/qbnbFn0MpuZZxUn5YZ8/+2/tyXFFHRyQoQ4YpNN1P/+qhZANiAAScPDyisb21\n\
237 GldmGksI5g82hjPRYscWNs/6pFxQTMcxABE+/1lWaryLR193ZD74VxVRIKDBluRs\n\
238 uuHi+VayOreTX1/qlUoxgBT+XTI0nTdLn6WwO6vVO1NIkGEVnYvB2eM=\n\
239 -----END PRIVATE KEY-----\n"];
240
241 /// A selection of EC keys that should have leading zeros in their coordinates
242 pub const P384_KEY_WITH_LEADING_ZEROS_PEM: &[&str] = &[
243 // 47 byte Y coordinate:
244 "-----BEGIN PRIVATE KEY-----\n\
245 ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDCzgVHCz7wgmSdb7/IixYik\n\
246 3AuQceCtBTiFrJpgpGFluwgLUR0S2NpzIuty4M7xU74=\n\
247 -----END PRIVATE KEY-----\n",
248 // 47 byte X coordinate:
249 "-----BEGIN PRIVATE KEY-----\n\
250 ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDBoW+8zbvwf5fYOS8YPyPEH\n\
251 jHP71Vr1MnRYRp/yG1wbthW2XEu0UWbp4qrZ5WTnZPg=\n\
252 -----END PRIVATE KEY-----\n",
253 // X & Y both have MSB set, and some stacks will add a padding byte
254 "-----BEGIN PRIVATE KEY-----\n\
255 ME4CAQAwEAYHKoZIzj0CAQYFK4EEACIENzA1AgEBBDD2A69j5M/6oc6/WGoYln4t\n\
256 Alnn0C6kpJz1EVC+eH6y0YNrcGamz8pPY4NkzUB/tj4=\n\
257 -----END PRIVATE KEY-----\n",
258 ];
259
260 /// A selection of elliptic curve P-521 private keys.
261 pub const P521_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
262 MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBQuD8Db3jT2yPYR5t\n\
263 Y1ZqESxOe4eBWzekFKg7cjVWsSiJEvWTPC1H7CLtXQBHZglO90dwMt4flL91xHkl\n\
264 iZOzyHahgYkDgYYABAHACwmmKkZu01fp1QTTTQ0cv7IAfYv9FEBz8yfhNGPnI2WY\n\
265 iH1/lYeCfYc9d33aSc/ELY9+vIFzVStJumS/B/WTewEhxVomlKPAkUJeLdCaK5av\n\
266 nlUNj7pNQ/5v5FZVxmoFJvAtUAnZqnJqo/QkLtEnmKlzpLte2LuwTPZhG35z0HeL\n\
267 2g==\n\
268 -----END PRIVATE KEY-----\n"];
269
270 /// A selection of 2048-bit RSA private keys.
271 pub const RSA2048_KEY_PEM: &[&str] = &["-----BEGIN PRIVATE KEY-----\n\
272 MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDbOJh7Ys7CuIju\n\
273 VVKMlFlZWwDEGBX5bVYD/xNBNNF1FY9bOcV/BG20IwoZkdV0N+vm8eWSuv/uwIJp\n\
274 sN2PMWPAEIWbPGGMnSdePpkwrdpFFywhEQqUrfdCFXZ8zeF85Nz5mL8ysl4vlMsL\n\
275 mbErCkrq++K0lzs+k7w/FtPCgs4M3WypJfZef5zM0CGWxpHZGoUGm0HW9fen4sv8\n\
276 hTmMGNY/r0SJhdZREGmiGCx2v+ksOEBon1r/6QKVTP8S73XFsyNCWyop0hYTakut\n\
277 D3HtJ5sWzu2RU8rrch3Txinz0jpGF8PATHk35YMw/9jwwwSqjDw+pOQcYk8SviAf\n\
278 glZf8aZlAgMBAAECggEAAS67PK67tuaOWywJSWHLsWGmqJ4I2tZiTzCT9EZ2MOVx\n\
279 +4ZChNfjHUsskXUp4XNL/FE0J3WvhEYjXR1u+L37nvqc48mJpjoPN7o/CMb6rM/J\n\
280 +ly9A2ZOvEB4ppOYDYh5QVDm7/otmvEMzJxuUOpvxYxqnJpAPgl9dBpNQ0nSt3YX\n\
281 jJS4+vuzQpwwSTfchpcCZYU3AX9DpQpxnrLX3/7d3GTs2NuedmSwRz+mCfwaOlFk\n\
282 jdrJ2uJJrDLcK6yhSdsE9aNgKkmX6aNLhxbbCFTyDNiGY5HHayyL3mVvyaeovYcn\n\
283 ZS+Z+0TJGCgXDRHHSFyIAsgVonxHfn49x9uvfpuMFQKBgQD2cVp26+aQgt46ajVv\n\
284 yn4fxbNpfovL0pgtSjo7ekZOWYJ3Is1SDmnni8k1ViKgUYC210dTTlrljxUil8T0\n\
285 83e03k2xasDi2c+h/7JFYJPDyZwIm1o19ciUwY73D54iJaRbrzEximFeA0h4LGKw\n\
286 Yjd4xkKMJw16CU00gInyI193BwKBgQDjuP0/QEEPpYvzpag5Ul4+h22K/tiOUrFj\n\
287 NuSgd+IvQG1hW48zHEa9vXvORQ/FteiQ708racz6ByqY+n2w6QdtdRMj7Hsyo2fk\n\
288 SEeNaLrR7Sif6MfkYajbSGFySDD82vj4Jt76vzdt3MjpZfs6ryPmnKLVPWNA3mnS\n\
289 4+u2J/+QMwKBgFfiJnugNnG0aaF1PKcoFAAqlYd6XEoMSL5l6QxK14WbP/5SR9wK\n\
290 TdQHsnI1zFVVm0wYy1O27o1MkCHs84zSwg6a9CPfyPdc60F/GMjK3wcD/4PGOs5h\n\
291 Xu1FdUE/rYnJ2KnleOqMyZooG5DXaz4xWEzWjubCCnlJleGyMP9LhADDAoGAR/jK\n\
292 iXgcV/6haeMcdOl0gdy5oWmENg8qo0nRHmplYTvCljei3at9LDC79WhcYMdqdow8\n\
293 AGOS9h7XtrvMh+JOh6it4Pe3xDxi9IJnoujLytditI+Uxbib7ppEuiLY4MGwWHWo\n\
294 maVftmhGU4X4zgZWmWc+C5k4SmNBHPcOI2cm3YMCgYB5/Ni+tBxng0S/PRAtwCeG\n\
295 dVnQnYvS2C5nHCn9D5rmAcVXUKrIJ1/1K4se8vQ15DDcpuBF1SejYTJzdUP8Zgcs\n\
296 p8wVq7neK8uSsmG+AfUgxMjbetoAVTP3L8+GbjocznR9auB7BEjFVO25iYSiTp/w\n\
297 NNzbIKQRDN+c3vUpneJcuw==\n\
298 -----END PRIVATE KEY-----\n"];
299 }
300