Skip to content

Commit d323073

Browse files
bkioshnstevenj
andauthored
feat(rust/c509-certificate): Update to version 11 (#22)
* fix: remove PEN supported * fix(rust/c509-certificate): time * fix(rust/c509-certificate): handle if issuer not set, set to subject * fix(rust/c509-certificate): remove rdn * fix(rust/c509-certificate): change certificate version number * fix(rust/c509-certificate): rearrange tbscertificate * fix(rust/c509-certificate): update docs * fix(rust/c509-certificate): format + err handling * fix(rust/c509-certificate): Rename functions + add necessary functions (#26) * fix: naming + add neccesary function * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): cleanup --------- Co-authored-by: Steven Johnson <[email protected]> * fix(rust/c509-certificate): CBOR decode encode helper (#27) * fix: naming + add neccesary function * fix(rust/c509-certificate): add encode + decode helper functions * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): remove unnecessary allow(dead_code) * fix(rust/c509-certificate): generic encode and decode functions * test(rust/c509-certificate): Fix test for draft11 (#28) * fix: naming + add neccesary function * fix(rust/c509-certificate): add encode + decode helper functions * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): cleanup * fix(rust/c509-certificate): remove unnecessary allow(dead_code) * fix(rust/c509-certificate): fix encode decode Name * chore(rust/c509-certificate): fix comment * fix(rust/c509-certificate): add + rewrite test cases * chore(rust/c509-certificate): earthly no cache * chore(rust/c509-certificate): earthly no cache * chore(rust/c509-certificate): earthly no cache * chore(rust/c509-certificate): remove earthly no-cache * fix(rust/c509-certificate): add more test comments * fix(rust/c509-certificate): clippy lints --------- Co-authored-by: Steven Johnson <[email protected]> Co-authored-by: Steven Johnson <[email protected]> * test(rust/c509-certificate): break the cache * fix(rust): Lint and lintfix locally in release build mode * test(rust/c509-certificate): rename tbs_cert.rs to cert_tbs.rs to test caching in CI * docs(rust/cardano-chain-follower): Fix list of enhancements we could do to the chain follower * ci(rust): Don't break the cache now in a build, but do check the cached src == the expected src * test(rust): In an abundance of caution make sure cached src isn't used in the cache check target * fix(rust) earthfile Signed-off-by: bkioshn <[email protected]> * fix(rust/c509-certificate): update docs (#39) * fix(rust/c509-certificate): docs Signed-off-by: bkioshn <[email protected]> * fix(rust/c509-certificate): spelling Signed-off-by: bkioshn <[email protected]> * fix(rust/c509-certificate): anyhow format Signed-off-by: bkioshn <[email protected]> * docs(rust/c509-certificate): make doc clearer Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]> Co-authored-by: Steven Johnson <[email protected]> Co-authored-by: Steven Johnson <[email protected]>
1 parent 43cd0a7 commit d323073

File tree

35 files changed

+1404
-1363
lines changed

35 files changed

+1404
-1363
lines changed
Lines changed: 39 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,64 @@
11
; This c509 Certificate format is based upon:
2-
; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/
2+
; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/
33
; And is restricted/customized to better enable compatibility with Plutus scripts
4-
; that would consume them, without loosing necessary features of x509
4+
; that would consume them, without losing necessary features of x509
55
; Not all x509 features are supported and some fields have different semantics to improve
66
; certificate size and ability to be processed by Plutus Scripts.
77

8-
; cspell: words reencoded, biguint
8+
; cspell: words reencoded, biguint, stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw
99

10-
C509CertificatePlutusRestrictedSubset = [ TBSCertificate, issuerSignatureValue: ed25519Signature, ]
10+
C509CertificatePlutusRestrictedSubset = [
11+
TBSCertificate,
12+
issuerSignatureValue: ed25519Signature
13+
]
1114

1215
; The elements of the following group are used in a CBOR Sequence:
1316
TBSCertificate = (
14-
c509CertificateType: &c509CertificateTypeValues, ; Always 0
15-
certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended.
16-
issuer: Name, ; This could be an on-chain reference to the issuer cert, what would be the best way? Transaction hash/cert hash?
17-
validityNotBefore: Time, ; c509 uses UTC
18-
validityNotAfter: Time, ; c509 uses UTC
19-
subject: Name, ; Reference to on-chain keys related to this certificate
20-
subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
21-
subjectPublicKey: subjectPublicKey, ; Ed25519 public key
22-
extensions: Extensions, ; No extensions are currently supported must be set to []
23-
issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
17+
c509CertificateType: int, ; Always 2 as a natively signed
18+
certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended.
19+
issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
20+
issuer: Name / null, ; If the 'issuer' field is identical to the 'subject' field (in case of self-signed), then it must be encoded as CBOR null
21+
; This could be an on-chain reference to the issuer cert. What would be the best way? Transaction hash/cert hash?
22+
validityNotBefore: ~time, ; c509 uses UTC
23+
validityNotAfter: ~time / null, ; c509 uses UTC, no expiration date must be set to null
24+
subject: Name, ; Reference to on-chain keys related to this certificate
25+
subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
26+
subjectPublicKey: subjectPublicKey, ; Ed25519 public key
27+
extensions: Extensions ; Set to [] if no Extensions provided
2428
)
2529

26-
; 0 = Native CBOR Certificate type
27-
; 1 = reencoded-der-cert - Not supported in this restricted version of the format.
28-
c509CertificateTypeValues = ( native-cbor: 0,
29-
; reencoded-der: 1 ; Not supported in this restricted encoding format
30-
)
31-
32-
CertificateSerialNumber = biguint
33-
34-
Name = [ * RelativeDistinguishedName ]
35-
/ text
36-
/ bytes
37-
38-
RelativeDistinguishedName = Attribute / [ 2* Attribute ]
30+
CertificateSerialNumber = ~biguint
3931

40-
Attribute = (
41-
( attributeType: int, attributeValue: text )
42-
// ( attributeType: oid, attributeValue: bytes )
43-
// ( attributeType: pen, attributeValue: bytes )
44-
// CardanoPublicKey
45-
)
46-
47-
subjectPublicKey = bytes .size (32..32); Ed25519 public key stored in bytes, adjust size of this if other key types are supported.
32+
; Currently ONLY AlgorithmIdentifier int(12) - Ed25519 is supported.
33+
; oid and [ algorithm: oid, parameters: bytes ] are not supported by Plutus.
34+
AlgorithmIdentifier = int / ~oid / [ algorithm: ~oid, parameters: bytes ]
4835

49-
; This is a completely custom Attribute for the RelativeDistinguishedName which is only for use with Plutus scripts.
50-
; attributeType = The type of Cardano key we associate with this certificate.
51-
; proof = Does the transaction require proof that the key is owned by the transaction signer?
52-
; attributeValue = The Cardano public key hash of the attribute type
36+
Name = [ * Attribute ] / text / bytes
5337

54-
CardanoPublicKey = ( attributeType: &cardanoKeyTypes proof: bool, attributeValue: bytes .size (28..28) )
38+
Attribute = ( attributeType: int, attributeValue: text )
39+
// ( attributeType: ~oid, attributeValue: bytes )
5540

56-
cardanoKeyTypes = (
57-
paymentKeyHash: 0,
58-
stakeKeyHash: 1,
59-
drepVerificationKeyHash: 2,
60-
ccColdVerificationKeyHash: 3,
61-
ccHotVerificationKeyHash: 4,
62-
)
41+
subjectPublicKey = bytes .size (32..32) ; Ed25519 public key stored in bytes, adjust size if other key types are supported.
6342

64-
; Plutus will need to convert the Unix epoch timestamp to the nearest slot number
43+
; For ~time, Plutus will need to convert the Unix epoch timestamp to the nearest slot number
6544
; validityNotBefore rounds up to the next Slot after that time.
6645
; validityNotAfter rounds down to the next Slot before that time.
67-
Time = ( ~time / null )
68-
69-
ed25519Signature = bstr .size 64; Ed25519 signature must be tagged to identify their type.
7046

47+
ed25519Signature = bstr .size 64 ; Ed25519 signature must be tagged to identify their type.
7148

72-
; Currently ONLY AlgorithmIdentifier int(12) - Ed25519 is supported.
73-
; oid and [ algorithm: oid, parameters: bytes ] are not supported by Plutus.
74-
AlgorithmIdentifier = (int
75-
/ ~oid
76-
/ [ algorithm: ~oid, parameters: bytes ])
49+
; The only Extension supported is int(3) = SubjectAltName where GeneralNames need to be
50+
; int(6) = uniformResourceIdentifier.
51+
; This uniformResourceIdentifier must conform to the URI based line in CIP-0134:
52+
; https://github.com/input-output-hk/catalyst-CIPs/tree/cip13-simple-cardano-address-extension/CIP-0134
53+
; for example, web+cardano://addr/stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw
7754

78-
; Extensions are not currently supported by plutus and should be set to []
79-
; Any extensions present in the certificate will be ignored by plutus scripts.
8055
Extensions = [ * Extension ] / int
8156

8257
Extension = (
83-
( extensionID: int, extensionValue: any )
84-
// ( extensionID: ~oid, ? critical: true, extensionValue: bytes )
85-
// ( extensionID: pen, ? critical: true, extensionValue: bytes )
58+
( extensionID: int, extensionValue: any )
59+
// ( extensionID: ~oid, ? critical: true, extensionValue: bytes )
8660
)
61+
62+
SubjectAltName = GeneralNames / text
63+
GeneralNames = [ + GeneralName ]
64+
GeneralName = ( GeneralNameType: int, GeneralNameValue: any )

rust/Justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ code-format:
2626

2727
# Lint the rust code
2828
code-lint:
29-
cargo lintfix
30-
cargo lint
29+
cargo lintfix -r
30+
cargo lint -r
3131

3232
# Pre Push Checks
3333
pre-push: sync-cfg code-format code-lint license-check

rust/c509-certificate/examples/cli/data/cert_sample_1.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"self_signed": true,
3-
"c509_certificate_type": 0,
3+
"c509_certificate_type": 2,
44
"certificate_serial_number": 128269,
5+
"issuer_signature_algorithm": null,
56
"issuer": [
67
{
78
"oid": "2.5.4.3",
@@ -24,6 +25,5 @@
2425
"value": { "int": 1 },
2526
"critical": false
2627
}
27-
],
28-
"issuer_signature_algorithm": null
28+
]
2929
}

rust/c509-certificate/examples/cli/main.rs

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ use std::{
88

99
use asn1_rs::{oid, Oid};
1010
use c509_certificate::{
11+
attributes::attribute::Attribute,
1112
big_uint::UnwrappedBigUint,
13+
cert_tbs::TbsCert,
1214
extensions::Extensions,
1315
issuer_sig_algo::IssuerSignatureAlgorithm,
14-
name::{rdn::RelativeDistinguishedName, Name, NameValue},
16+
name::{Name, NameValue},
1517
signing::{PrivateKey, PublicKey},
1618
subject_pub_key_algo::SubjectPubKeyAlgorithm,
17-
tbs_cert::TbsCert,
1819
time::Time,
1920
};
2021
use chrono::{DateTime, Utc};
@@ -97,22 +98,25 @@ impl Cli {
9798
struct C509Json {
9899
/// Indicate whether the certificate is self-signed.
99100
self_signed: bool,
100-
/// Optional certificate type, if not provided, set to 0 as self-signed.
101+
/// Optional certificate type, if not provided, set to 2 as self-signed.
101102
certificate_type: Option<u8>,
102103
/// Optional serial number of the certificate,
103104
/// if not provided, a random number will be generated.
104105
serial_number: Option<UnwrappedBigUint>,
106+
/// Optional issuer signature algorithm of the certificate,
107+
/// if not provided, set to Ed25519.
108+
issuer_signature_algorithm: Option<IssuerSignatureAlgorithm>,
105109
/// Optional issuer of the certificate,
106110
/// if not provided, issuer is the same as subject.
107-
issuer: Option<RelativeDistinguishedName>,
111+
issuer: Option<Vec<Attribute>>,
108112
/// Optional validity not before date,
109113
/// if not provided, set to current time.
110114
validity_not_before: Option<String>,
111115
/// Optional validity not after date,
112116
/// if not provided, set to no expire date 9999-12-31T23:59:59+00:00.
113117
validity_not_after: Option<String>,
114-
/// Relative distinguished name of the subject.
115-
subject: RelativeDistinguishedName,
118+
/// Attributes of the subject.
119+
subject: Vec<Attribute>,
116120
/// Optional subject public key algorithm of the certificate,
117121
/// if not provided, set to Ed25519.
118122
subject_public_key_algorithm: Option<SubjectPubKeyAlgorithm>,
@@ -121,9 +125,6 @@ struct C509Json {
121125
subject_public_key: String,
122126
/// Extensions of the certificate.
123127
extensions: Extensions,
124-
/// Optional issuer signature algorithm of the certificate,
125-
/// if not provided, set to Ed25519.
126-
issuer_signature_algorithm: Option<IssuerSignatureAlgorithm>,
127128
/// Optional issuer signature value of the certificate.
128129
#[serde(skip_deserializing)]
129130
issuer_signature_value: Option<Vec<u8>>,
@@ -133,9 +134,9 @@ struct C509Json {
133134
const ED25519: (Oid, Option<String>) = (oid!(1.3.101 .112), None);
134135

135136
/// Integer indicate that certificate is self-signed.
136-
/// 0 for Natively Signed C509 Certificate following X.509 v3
137-
/// 1 for CBOR re-encoding of X.509 v3 Certificate
138-
const SELF_SIGNED_INT: u8 = 0;
137+
/// 2 for Natively Signed C509 Certificate following X.509 v3
138+
/// 3 for CBOR re-encoding of X.509 v3 Certificate
139+
const SELF_SIGNED_INT: u8 = 2;
139140

140141
// -------------------generate-----------------------
141142

@@ -159,7 +160,12 @@ fn generate(
159160

160161
// Parse validity dates or use defaults
161162
// Now for not_before date
162-
let not_before = parse_or_default_date(c509_json.validity_not_before, Utc::now().timestamp())?;
163+
let now_timestamp: u64 = Utc::now()
164+
.timestamp()
165+
.try_into()
166+
.map_err(|_| anyhow::anyhow!("Current timestamp is invalid"))?;
167+
168+
let not_before = parse_or_default_date(c509_json.validity_not_before, now_timestamp)?;
163169
// Default as expire date for not_after
164170
// Expire date = 9999-12-31T23:59:59+00:00 as mention in the C509 document
165171
let not_after = parse_or_default_date(
@@ -175,18 +181,18 @@ fn generate(
175181
let tbs = TbsCert::new(
176182
c509_json.certificate_type.unwrap_or(SELF_SIGNED_INT),
177183
serial_number,
178-
Name::new(NameValue::RelativeDistinguishedName(issuer)),
184+
c509_json
185+
.issuer_signature_algorithm
186+
.unwrap_or(IssuerSignatureAlgorithm::new(key_type.0.clone(), ED25519.1)),
187+
Some(Name::new(NameValue::Attribute(issuer))),
179188
Time::new(not_before),
180189
Time::new(not_after),
181-
Name::new(NameValue::RelativeDistinguishedName(c509_json.subject)),
190+
Name::new(NameValue::Attribute(c509_json.subject)),
182191
c509_json
183192
.subject_public_key_algorithm
184-
.unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0.clone(), key_type.1)),
193+
.unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0, key_type.1)),
185194
public_key.to_bytes(),
186195
c509_json.extensions.clone(),
187-
c509_json
188-
.issuer_signature_algorithm
189-
.unwrap_or(IssuerSignatureAlgorithm::new(key_type.0, ED25519.1)),
190196
);
191197

192198
let cert = c509_certificate::generate(&tbs, private_key)?;
@@ -213,9 +219,8 @@ fn write_to_output_file(output: PathBuf, data: &[u8]) -> anyhow::Result<()> {
213219
/// If self-signed is true, issuer is the same as subject.
214220
/// Otherwise, issuer must be present.
215221
fn determine_issuer(
216-
self_signed: bool, issuer: Option<RelativeDistinguishedName>,
217-
subject: RelativeDistinguishedName,
218-
) -> anyhow::Result<RelativeDistinguishedName> {
222+
self_signed: bool, issuer: Option<Vec<Attribute>>, subject: Vec<Attribute>,
223+
) -> anyhow::Result<Vec<Attribute>> {
219224
if self_signed {
220225
Ok(subject)
221226
} else {
@@ -229,7 +234,7 @@ fn validate_certificate_type(
229234
) -> anyhow::Result<()> {
230235
if self_signed && certificate_type.unwrap_or(SELF_SIGNED_INT) != SELF_SIGNED_INT {
231236
return Err(anyhow::anyhow!(
232-
"Certificate type must be 0 if self-signed is true"
237+
"Certificate type must be {SELF_SIGNED_INT} if self-signed is true"
233238
));
234239
}
235240
Ok(())
@@ -249,13 +254,17 @@ fn get_key_type(key_type: &Option<String>) -> anyhow::Result<(Oid<'static>, Opti
249254
}
250255
}
251256

252-
/// Parse date string to i64.
253-
fn parse_or_default_date(date_option: Option<String>, default: i64) -> Result<i64, anyhow::Error> {
257+
/// Parse date string to u64.
258+
fn parse_or_default_date(date_option: Option<String>, default: u64) -> Result<u64, anyhow::Error> {
254259
match date_option {
255260
Some(date) => {
256261
DateTime::parse_from_rfc3339(&date)
257-
.map(|dt| dt.timestamp())
258-
.map_err(|e| anyhow::anyhow!(format!("Failed to parse date {date}: {e}",)))
262+
.map(|dt| {
263+
dt.timestamp()
264+
.try_into()
265+
.map_err(|_| anyhow::anyhow!("Timestamp is invalid"))
266+
})?
267+
.map_err(|e| anyhow::anyhow!("Failed to parse date {date}: {e}"))
259268
},
260269
None => Ok(default),
261270
}
@@ -288,22 +297,26 @@ fn decode(file: &PathBuf, output: Option<PathBuf>) -> anyhow::Result<()> {
288297
let mut d = minicbor::Decoder::new(&cert);
289298
let c509 = c509_certificate::c509::C509::decode(&mut d, &mut ())?;
290299

291-
let tbs_cert = c509.get_tbs_cert();
292-
let is_self_signed = tbs_cert.get_c509_certificate_type() == SELF_SIGNED_INT;
300+
let tbs_cert = c509.tbs_cert();
301+
let is_self_signed = tbs_cert.c509_certificate_type() == SELF_SIGNED_INT;
293302
let c509_json = C509Json {
294303
self_signed: is_self_signed,
295-
certificate_type: Some(tbs_cert.get_c509_certificate_type()),
296-
serial_number: Some(tbs_cert.get_certificate_serial_number().clone()),
297-
issuer: Some(extract_relative_distinguished_name(tbs_cert.get_issuer())?),
298-
validity_not_before: Some(time_to_string(tbs_cert.get_validity_not_before().to_i64())?),
299-
validity_not_after: Some(time_to_string(tbs_cert.get_validity_not_after().to_i64())?),
300-
subject: extract_relative_distinguished_name(tbs_cert.get_subject())?,
301-
subject_public_key_algorithm: Some(tbs_cert.get_subject_public_key_algorithm().clone()),
304+
certificate_type: Some(tbs_cert.c509_certificate_type()),
305+
serial_number: Some(tbs_cert.certificate_serial_number().clone()),
306+
issuer_signature_algorithm: Some(tbs_cert.issuer_signature_algorithm().clone()),
307+
issuer: Some(extract_attributes(tbs_cert.issuer())?),
308+
validity_not_before: Some(time_to_string(
309+
tbs_cert.validity_not_before().clone().into(),
310+
)?),
311+
validity_not_after: Some(time_to_string(
312+
tbs_cert.validity_not_after().clone().into(),
313+
)?),
314+
subject: extract_attributes(tbs_cert.subject())?,
315+
subject_public_key_algorithm: Some(tbs_cert.subject_public_key_algorithm().clone()),
302316
// Return a hex formation of the public key
303-
subject_public_key: tbs_cert.get_subject_public_key().encode_hex(),
304-
extensions: tbs_cert.get_extensions().clone(),
305-
issuer_signature_algorithm: Some(tbs_cert.get_issuer_signature_algorithm().clone()),
306-
issuer_signature_value: c509.get_issuer_signature_value().clone(),
317+
subject_public_key: tbs_cert.subject_public_key().encode_hex(),
318+
extensions: tbs_cert.extensions().clone(),
319+
issuer_signature_value: c509.issuer_signature_value().clone(),
307320
};
308321

309322
let data = serde_json::to_string(&c509_json)?;
@@ -316,18 +329,24 @@ fn decode(file: &PathBuf, output: Option<PathBuf>) -> anyhow::Result<()> {
316329
Ok(())
317330
}
318331

319-
/// Extract a `RelativeDistinguishedName` from a `Name`.
320-
fn extract_relative_distinguished_name(name: &Name) -> anyhow::Result<RelativeDistinguishedName> {
321-
match name.get_value() {
322-
NameValue::RelativeDistinguishedName(rdn) => Ok(rdn.clone()),
323-
_ => Err(anyhow::anyhow!("Expected RelativeDistinguishedName")),
332+
/// Extract a `Attributes` from a `Name`.
333+
fn extract_attributes(name: &Name) -> anyhow::Result<Vec<Attribute>> {
334+
match name.value() {
335+
NameValue::Attribute(attrs) => Ok(attrs.clone()),
336+
_ => Err(anyhow::anyhow!("Expected Attributes")),
324337
}
325338
}
326339

327340
/// Convert time in i64 to string.
328-
fn time_to_string(time: i64) -> anyhow::Result<String> {
329-
let datetime =
330-
DateTime::from_timestamp(time, 0).ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?;
341+
fn time_to_string(time: u64) -> anyhow::Result<String> {
342+
// Attempt to convert the timestamp and handle errors if they occur
343+
let timestamp: i64 = time
344+
.try_into()
345+
.map_err(|e| anyhow::anyhow!("Failed to convert time: {:?}", e))?;
346+
347+
// Convert the timestamp to a DateTime and handle any potential errors
348+
let datetime = DateTime::from_timestamp(timestamp, 0)
349+
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?;
331350
Ok(datetime.to_rfc3339())
332351
}
333352

0 commit comments

Comments
 (0)