Skip to content

Commit 065bd81

Browse files
Add logic to decode and verify certificate
A DER encoded certificate can now be decoded and have it's signature verified
1 parent 5247451 commit 065bd81

File tree

4 files changed

+322
-35
lines changed

4 files changed

+322
-35
lines changed

.github/workflows/ci.yaml

-34
Original file line numberDiff line numberDiff line change
@@ -258,39 +258,6 @@ jobs:
258258
with:
259259
files: lcov.info
260260

261-
# Ensure that verifier is able to build without alloc.
262-
build-no-alloc:
263-
runs-on: ubuntu-22.04
264-
needs:
265-
- lint
266-
strategy:
267-
matrix:
268-
target:
269-
- thumbv6m-none-eabi
270-
- thumbv7m-none-eabi
271-
- thumbv8m.main-none-eabi
272-
- aarch64-linux-android
273-
- aarch64-apple-ios
274-
steps:
275-
- uses: actions/checkout@v3
276-
with:
277-
submodules: recursive
278-
# The building of mc-sgx-core-types needs C headers. We leverage the
279-
# SGX_SDK to get a somewhat portable version of the C headers.
280-
- uses: mobilecoinfoundation/actions/sgxsdk@main
281-
with:
282-
version: 2.18.100.3
283-
- uses: dtolnay/rust-toolchain@master
284-
with:
285-
toolchain: nightly-2023-01-04
286-
targets: ${{ matrix.target }},x86_64-unknown-linux-gnu
287-
components: rust-src
288-
- uses: r7kamura/rust-problem-matchers@v1
289-
- name: Build no alloc crate on various platforms
290-
run: |
291-
CFLAGS="-isystem$SGX_SDK/include/tlibc" cargo +nightly-2023-01-04 \
292-
build -Z build-std=core --target ${{ matrix.target }}
293-
294261
notify:
295262
runs-on: ubuntu-latest
296263
if: github.event_name == 'push' && failure()
@@ -300,7 +267,6 @@ jobs:
300267
- sort
301268
- clippy
302269
- build
303-
- build-no-alloc
304270
- test
305271
- doc
306272
- coverage

verifier/Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "mc-attestation-verifier"
33
version = "0.1.0"
44
authors = { workspace = true }
55
# See https://crates.io/category_slugs for valid categories
6-
categories = ["authentication", "no-std", "no-std::no-alloc"]
6+
categories = ["authentication", "no-std"]
77
description = "SGX Enclave Attestation Report Verification"
88
edition = { workspace = true }
99
# See https://crates.io/keywords for the common keywords
@@ -14,9 +14,13 @@ repository = { workspace = true }
1414
rust-version = { workspace = true }
1515

1616
[dependencies]
17+
const-oid = { version = "0.9.2", default-features = false }
1718
displaydoc = { version = "0.2.1", default-features = false }
1819
mc-sgx-core-types = "0.5.0"
20+
p256 = { version = "0.13.0", default-features = false, features = ["ecdsa"] }
21+
pem-rfc7468 = { version = "0.7.0", default-features = false, features = ["alloc"] }
1922
subtle = { version = "2.4.0", default-features = false }
23+
x509-cert = { version = "0.2.0", default-features = false }
2024

2125
[dev-dependencies]
2226
mc-sgx-core-sys-types = "0.5.0"

verifier/src/certs.rs

+314
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
// Copyright (c) 2023 The MobileCoin Foundation
2+
3+
//! Verifier(s) for [`CertificationData`]
4+
5+
use p256::ecdsa::signature::Verifier;
6+
use p256::ecdsa::{Signature, VerifyingKey};
7+
use x509_cert::der::{Decode, Encode};
8+
use x509_cert::Certificate as X509Certificate;
9+
10+
/// Offset from the start of a certificate to the "to be signed" (TBS) portion
11+
/// of the certificate.
12+
const TBS_OFFSET: usize = 4;
13+
14+
pub type Result<T> = core::result::Result<T, Error>;
15+
16+
/// Error type for decoding and verifying certificates.
17+
#[derive(Debug, displaydoc::Display, PartialEq, Eq)]
18+
pub enum Error {
19+
/// An error occurred decoding the signature from a certificate
20+
SignatureDecoding,
21+
/// The certification signature does not match with the verifying key
22+
SignatureVerification,
23+
/// An error occurred decoding the certificate
24+
CertificateDecoding(x509_cert::der::Error),
25+
/// An error occurred decoding the key from a certificate
26+
KeyDecoding,
27+
}
28+
29+
impl From<x509_cert::der::Error> for Error {
30+
fn from(src: x509_cert::der::Error) -> Self {
31+
Error::CertificateDecoding(src)
32+
}
33+
}
34+
35+
/// A certificate whose signature has not been verified.
36+
#[derive(Debug, PartialEq, Eq)]
37+
pub struct UnverifiedCertificate<'a> {
38+
// In order to verify the signature, we need to access the original DER
39+
// bytes
40+
der_bytes: &'a [u8],
41+
certificate: X509Certificate,
42+
// The signature and key are persisted here since they are fallible
43+
// operations and it's more ergonomic to fail fast than fail later for a
44+
// bad key or signature
45+
signature: Signature,
46+
key: VerifyingKey,
47+
}
48+
49+
/// A certificate whose signature has been verified.
50+
#[derive(Debug, PartialEq, Eq)]
51+
pub struct VerifiedCertificate<'a> {
52+
_der_bytes: &'a [u8],
53+
_certificate: X509Certificate,
54+
_signature: Signature,
55+
_key: VerifyingKey,
56+
}
57+
58+
impl<'a> UnverifiedCertificate<'a> {
59+
/// Verify the certificate signature.
60+
pub fn verify(self, key: &VerifyingKey) -> Result<VerifiedCertificate<'a>> {
61+
let tbs_length = self.certificate.tbs_certificate.encoded_len()?;
62+
let tbs_size = u32::from(tbs_length) as usize;
63+
let tbs_contents = &self.der_bytes[TBS_OFFSET..tbs_size + TBS_OFFSET];
64+
key.verify(tbs_contents, &self.signature)
65+
.map_err(|_| Error::SignatureVerification)?;
66+
Ok(VerifiedCertificate {
67+
_der_bytes: self.der_bytes,
68+
_certificate: self.certificate,
69+
_signature: self.signature,
70+
_key: self.key,
71+
})
72+
}
73+
}
74+
75+
/// Convert a DER-encoded certificate into an [`UnverifiedCertificate`].
76+
impl<'a> TryFrom<&'a [u8]> for UnverifiedCertificate<'a> {
77+
type Error = Error;
78+
79+
fn try_from(der_bytes: &'a [u8]) -> ::core::result::Result<Self, Self::Error> {
80+
let certificate = X509Certificate::from_der(der_bytes)?;
81+
let signature_bytes = certificate
82+
.signature
83+
.as_bytes()
84+
.ok_or(Error::SignatureDecoding)?;
85+
let signature =
86+
Signature::from_der(signature_bytes).map_err(|_| Error::SignatureDecoding)?;
87+
let key = VerifyingKey::from_sec1_bytes(
88+
certificate
89+
.tbs_certificate
90+
.subject_public_key_info
91+
.subject_public_key
92+
.as_bytes()
93+
.ok_or(Error::KeyDecoding)?,
94+
)
95+
.map_err(|_| Error::KeyDecoding)?;
96+
Ok(UnverifiedCertificate {
97+
der_bytes,
98+
certificate,
99+
signature,
100+
key,
101+
})
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
mod test {
107+
extern crate alloc;
108+
109+
use super::*;
110+
use const_oid::ObjectIdentifier;
111+
use yare::parameterized;
112+
113+
const LEAF_CERT: &str = "
114+
-----BEGIN CERTIFICATE-----
115+
MIIEjzCCBDSgAwIBAgIVAPtJxlxRlleZOb/spRh9U8K7AT/3MAoGCCqGSM49BAMC
116+
MHExIzAhBgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQK
117+
DBFJbnRlbCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNV
118+
BAgMAkNBMQswCQYDVQQGEwJVUzAeFw0yMjA2MTMyMTQ2MzRaFw0yOTA2MTMyMTQ2
119+
MzRaMHAxIjAgBgNVBAMMGUludGVsIFNHWCBQQ0sgQ2VydGlmaWNhdGUxGjAYBgNV
120+
BAoMEUludGVsIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkG
121+
A1UECAwCQ0ExCzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
122+
j/Ee1lkGJofDX745Ks5qxqu7Mk7Mqcwkx58TCSTsabRCSvobSl/Ts8b0dltKUW3j
123+
qRd+SxnPEWJ+jUw+SpzwWaOCAqgwggKkMB8GA1UdIwQYMBaAFNDoqtp11/kuSReY
124+
PHsUZdDV8llNMGwGA1UdHwRlMGMwYaBfoF2GW2h0dHBzOi8vYXBpLnRydXN0ZWRz
125+
ZXJ2aWNlcy5pbnRlbC5jb20vc2d4L2NlcnRpZmljYXRpb24vdjMvcGNrY3JsP2Nh
126+
PXByb2Nlc3NvciZlbmNvZGluZz1kZXIwHQYDVR0OBBYEFKy9gk624HzNnDyCw7QW
127+
nhmVfE31MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMIIB1AYJKoZIhvhN
128+
AQ0BBIIBxTCCAcEwHgYKKoZIhvhNAQ0BAQQQ36FQl3ntUr3KUwbEFvmRGzCCAWQG
129+
CiqGSIb4TQENAQIwggFUMBAGCyqGSIb4TQENAQIBAgERMBAGCyqGSIb4TQENAQIC
130+
AgERMBAGCyqGSIb4TQENAQIDAgECMBAGCyqGSIb4TQENAQIEAgEEMBAGCyqGSIb4
131+
TQENAQIFAgEBMBEGCyqGSIb4TQENAQIGAgIAgDAQBgsqhkiG+E0BDQECBwIBBjAQ
132+
BgsqhkiG+E0BDQECCAIBADAQBgsqhkiG+E0BDQECCQIBADAQBgsqhkiG+E0BDQEC
133+
CgIBADAQBgsqhkiG+E0BDQECCwIBADAQBgsqhkiG+E0BDQECDAIBADAQBgsqhkiG
134+
+E0BDQECDQIBADAQBgsqhkiG+E0BDQECDgIBADAQBgsqhkiG+E0BDQECDwIBADAQ
135+
BgsqhkiG+E0BDQECEAIBADAQBgsqhkiG+E0BDQECEQIBCzAfBgsqhkiG+E0BDQEC
136+
EgQQERECBAGABgAAAAAAAAAAADAQBgoqhkiG+E0BDQEDBAIAADAUBgoqhkiG+E0B
137+
DQEEBAYAkG7VAAAwDwYKKoZIhvhNAQ0BBQoBADAKBggqhkjOPQQDAgNJADBGAiEA
138+
1XJi0ht4hw8YtC6E4rYscp9bF+7UOhVGeKePA5TW2FQCIQCIUAaewOuWOIvstZN4
139+
V8Zu8NFCC4vFg+cZqO6QfezEaA==
140+
-----END CERTIFICATE-----
141+
";
142+
143+
const INTERMEDIATE_CA: &str = "
144+
-----BEGIN CERTIFICATE-----
145+
MIICmDCCAj6gAwIBAgIVANDoqtp11/kuSReYPHsUZdDV8llNMAoGCCqGSM49BAMC
146+
MGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD
147+
b3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw
148+
CQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHExIzAh
149+
BgNVBAMMGkludGVsIFNHWCBQQ0sgUHJvY2Vzc29yIENBMRowGAYDVQQKDBFJbnRl
150+
bCBDb3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNB
151+
MQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABL9q+NMp2IOg
152+
tdl1bk/uWZ5+TGQm8aCi8z78fs+fKCQ3d+uDzXnVTAT2ZhDCifyIuJwvN3wNBp9i
153+
HBSSMJMJrBOjgbswgbgwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqww
154+
UgYDVR0fBEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNl
155+
cnZpY2VzLmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFNDo
156+
qtp11/kuSReYPHsUZdDV8llNMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG
157+
AQH/AgEAMAoGCCqGSM49BAMCA0gAMEUCIQCJgTbtVqOyZ1m3jqiAXM6QYa6r5sWS
158+
4y/G7y8uIJGxdwIgRqPvBSKzzQagBLQq5s5A70pdoiaRJ8z/0uDz4NgV91k=
159+
-----END CERTIFICATE-----
160+
";
161+
162+
const ROOT_CA: &str = "
163+
-----BEGIN CERTIFICATE-----
164+
MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw
165+
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
166+
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
167+
BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG
168+
A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0
169+
aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
170+
AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7
171+
1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB
172+
uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ
173+
MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50
174+
ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV
175+
Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
176+
KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg
177+
AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=
178+
-----END CERTIFICATE-----
179+
";
180+
181+
#[parameterized(
182+
root = { ROOT_CA },
183+
intermediate = { INTERMEDIATE_CA },
184+
leaf = { LEAF_CERT },
185+
)]
186+
fn try_from_der(pem: &str) {
187+
let dedent = textwrap::dedent(pem);
188+
let (_, der_bytes) = pem_rfc7468::decode_vec(dedent.trim().as_bytes())
189+
.expect("Failed to decode DER from PEM");
190+
assert!(UnverifiedCertificate::try_from(der_bytes.as_slice()).is_ok());
191+
}
192+
193+
#[test]
194+
fn certificate_decoding_error_with_invalid_der() {
195+
let pem = textwrap::dedent(ROOT_CA);
196+
let (_, der_bytes) =
197+
pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM");
198+
assert!(matches!(
199+
UnverifiedCertificate::try_from(&der_bytes.as_slice()[1..]),
200+
Err(Error::CertificateDecoding(_))
201+
));
202+
}
203+
204+
#[test]
205+
fn signature_decoding_error() {
206+
let pem = textwrap::dedent(ROOT_CA);
207+
let (_, mut der_bytes) =
208+
pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM");
209+
210+
// The signature is and the end of the certificate.
211+
// If iether of the points are 0 it will fail to decode so we force the
212+
// last point to 0
213+
let last_point = der_bytes.len() - 32;
214+
der_bytes[last_point..].copy_from_slice(&[0; 32]);
215+
216+
assert_eq!(
217+
UnverifiedCertificate::try_from(der_bytes.as_slice()),
218+
Err(Error::SignatureDecoding)
219+
);
220+
}
221+
222+
#[test]
223+
fn key_decoding_error() {
224+
let pem = textwrap::dedent(ROOT_CA);
225+
let (_, mut der_bytes) =
226+
pem_rfc7468::decode_vec(pem.trim().as_bytes()).expect("Failed to decode DER from PEM");
227+
228+
// There isn't a good way to get the offset to the key, so we look for
229+
// the bytes that represent the key object identifier (OID)
230+
let key_oid = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
231+
let key_oid_bytes = key_oid.as_bytes();
232+
let key_oid_offset = der_bytes
233+
.windows(key_oid_bytes.len())
234+
.position(|window| window == key_oid_bytes)
235+
.expect("Failed to find key OID");
236+
237+
// 2 Bytes for the key tag [TYPE, SIZE]
238+
let key_offset = key_oid_offset + key_oid_bytes.len() + 2;
239+
let key_end = key_offset + 64;
240+
der_bytes[key_offset..key_end].copy_from_slice(&[0; 64]);
241+
242+
assert_eq!(
243+
UnverifiedCertificate::try_from(der_bytes.as_slice()),
244+
Err(Error::KeyDecoding)
245+
);
246+
}
247+
248+
#[test]
249+
fn verify_root_certificate() {
250+
let root = textwrap::dedent(ROOT_CA);
251+
let (_, der_bytes) =
252+
pem_rfc7468::decode_vec(root.trim().as_bytes()).expect("Failed to decode DER from PEM");
253+
let cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
254+
.expect("Failed to decode certificate from DER");
255+
256+
// The root certificate is self-signed, ideally this key will be stored
257+
// by the application.
258+
let key = cert.key;
259+
260+
assert_eq!(cert.verify(&key).is_ok(), true);
261+
}
262+
263+
#[test]
264+
fn verify_intermediate_certificate() {
265+
let root = textwrap::dedent(ROOT_CA);
266+
let (_, der_bytes) =
267+
pem_rfc7468::decode_vec(root.trim().as_bytes()).expect("Failed to decode DER from PEM");
268+
let root_cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
269+
.expect("Failed to decode certificate from DER");
270+
271+
let intermediate = textwrap::dedent(INTERMEDIATE_CA);
272+
let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes())
273+
.expect("Failed to decode DER from PEM");
274+
let cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
275+
.expect("Failed to decode certificate from DER");
276+
277+
assert_eq!(cert.verify(&root_cert.key).is_ok(), true);
278+
}
279+
280+
#[test]
281+
fn verify_leaf_certificate() {
282+
let intermediate = textwrap::dedent(INTERMEDIATE_CA);
283+
let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes())
284+
.expect("Failed to decode DER from PEM");
285+
let intermediate_cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
286+
.expect("Failed to decode certificate from DER");
287+
288+
let leaf = textwrap::dedent(LEAF_CERT);
289+
let (_, der_bytes) =
290+
pem_rfc7468::decode_vec(leaf.trim().as_bytes()).expect("Failed to decode DER from PEM");
291+
let cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
292+
.expect("Failed to decode certificate from DER");
293+
294+
assert_eq!(cert.verify(&intermediate_cert.key).is_ok(), true);
295+
}
296+
297+
#[test]
298+
fn verify_certificate_fails_with_wrong_key() {
299+
let intermediate = textwrap::dedent(INTERMEDIATE_CA);
300+
let (_, der_bytes) = pem_rfc7468::decode_vec(intermediate.trim().as_bytes())
301+
.expect("Failed to decode DER from PEM");
302+
let intermediate_cert = UnverifiedCertificate::try_from(der_bytes.as_slice())
303+
.expect("Failed to decode certificate from DER");
304+
305+
// The intermediate cert should *not* be self signed so using it's key
306+
// should fail verification
307+
let key = intermediate_cert.key;
308+
309+
assert_eq!(
310+
intermediate_cert.verify(&key),
311+
Err(Error::SignatureVerification)
312+
);
313+
}
314+
}

0 commit comments

Comments
 (0)