Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b1b3196
fix: remove PEN supported
bkioshn Sep 11, 2024
8a045e4
fix(rust/c509-certificate): time
bkioshn Sep 11, 2024
4b73607
fix(rust/c509-certificate): handle if issuer not set, set to subject
bkioshn Sep 11, 2024
3e70fc0
fix(rust/c509-certificate): remove rdn
bkioshn Sep 11, 2024
afe628d
fix(rust/c509-certificate): change certificate version number
bkioshn Sep 11, 2024
0e30d15
fix(rust/c509-certificate): rearrange tbscertificate
bkioshn Sep 11, 2024
069640b
fix(rust/c509-certificate): update docs
bkioshn Sep 12, 2024
b2d22c8
fix(rust/c509-certificate): format + err handling
bkioshn Sep 12, 2024
7cb3f21
Merge branch 'main' into feat/c509-v11
bkioshn Sep 12, 2024
b94854d
Merge branch 'main' into feat/c509-v11
stevenj Sep 17, 2024
c50834a
fix(rust/c509-certificate): Rename functions + add necessary function…
bkioshn Sep 17, 2024
ddf93d2
fix(rust/c509-certificate): CBOR decode encode helper (#27)
bkioshn Sep 19, 2024
61984b8
Merge branch 'main' into feat/c509-v11
stevenj Sep 21, 2024
a88a032
Merge branch 'main' into feat/c509-v11
stevenj Sep 21, 2024
b288110
test(rust/c509-certificate): Fix test for draft11 (#28)
bkioshn Sep 21, 2024
e49224d
test(rust/c509-certificate): break the cache
stevenj Sep 21, 2024
c04d0b1
fix(rust): Lint and lintfix locally in release build mode
stevenj Sep 21, 2024
01009e7
test(rust/c509-certificate): rename tbs_cert.rs to cert_tbs.rs to tes…
stevenj Sep 21, 2024
84983e4
docs(rust/cardano-chain-follower): Fix list of enhancements we could …
stevenj Sep 23, 2024
9b9e37f
ci(rust): Don't break the cache now in a build, but do check the cach…
stevenj Sep 23, 2024
391cb80
test(rust): In an abundance of caution make sure cached src isn't use…
stevenj Sep 24, 2024
17e28dc
Merge branch 'main' into feat/c509-v11
stevenj Sep 24, 2024
91fc0eb
Merge branch 'main' into feat/c509-v11
bkioshn Sep 30, 2024
edc69a2
Merge branch 'main' into feat/c509-v11
stevenj Sep 30, 2024
3b7bf5b
fix(rust) earthfile
bkioshn Sep 30, 2024
389485a
Merge branch 'main' into feat/c509-v11
bkioshn Oct 2, 2024
84fe964
fix(rust/c509-certificate): update docs (#39)
bkioshn Oct 3, 2024
b92837b
Merge branch 'main' into feat/c509-v11
stevenj Oct 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,86 +1,64 @@
; This c509 Certificate format is based upon:
; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/09/
; https://datatracker.ietf.org/doc/draft-ietf-cose-cbor-encoded-cert/11/
; And is restricted/customized to better enable compatibility with Plutus scripts
; that would consume them, without loosing necessary features of x509
; that would consume them, without losing necessary features of x509
; Not all x509 features are supported and some fields have different semantics to improve
; certificate size and ability to be processed by Plutus Scripts.

; cspell: words reencoded, biguint
; cspell: words reencoded, biguint, stake1uyehkck0lajq8gr28t9uxnuvgcqrc6070x3k9r8048z8y5gh6ffgw

C509CertificatePlutusRestrictedSubset = [ TBSCertificate, issuerSignatureValue: ed25519Signature, ]
C509CertificatePlutusRestrictedSubset = [
TBSCertificate,
issuerSignatureValue: ed25519Signature
]

; The elements of the following group are used in a CBOR Sequence:
TBSCertificate = (
c509CertificateType: &c509CertificateTypeValues, ; Always 0
certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended.
issuer: Name, ; This could be an on-chain reference to the issuer cert, what would be the best way? Transaction hash/cert hash?
validityNotBefore: Time, ; c509 uses UTC
validityNotAfter: Time, ; c509 uses UTC
subject: Name, ; Reference to on-chain keys related to this certificate
subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
subjectPublicKey: subjectPublicKey, ; Ed25519 public key
extensions: Extensions, ; No extensions are currently supported must be set to []
issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
c509CertificateType: int, ; Always 2 as a natively signed
certificateSerialNumber: CertificateSerialNumber, ; Can be ignored/set to 0 or used as intended.
issuerSignatureAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
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
; This could be an on-chain reference to the issuer cert. What would be the best way? Transaction hash/cert hash?
validityNotBefore: ~time, ; c509 uses UTC
validityNotAfter: ~time / null, ; c509 uses UTC, no expiration date must be set to null
subject: Name, ; Reference to on-chain keys related to this certificate
subjectPublicKeyAlgorithm: AlgorithmIdentifier, ; Must be int(12) = Ed25519
subjectPublicKey: subjectPublicKey, ; Ed25519 public key
extensions: Extensions ; Set to [] if no Extensions provided
)

; 0 = Native CBOR Certificate type
; 1 = reencoded-der-cert - Not supported in this restricted version of the format.
c509CertificateTypeValues = ( native-cbor: 0,
; reencoded-der: 1 ; Not supported in this restricted encoding format
)

CertificateSerialNumber = biguint

Name = [ * RelativeDistinguishedName ]
/ text
/ bytes

RelativeDistinguishedName = Attribute / [ 2* Attribute ]
CertificateSerialNumber = ~biguint

Attribute = (
( attributeType: int, attributeValue: text )
// ( attributeType: oid, attributeValue: bytes )
// ( attributeType: pen, attributeValue: bytes )
// CardanoPublicKey
)

subjectPublicKey = bytes .size (32..32); Ed25519 public key stored in bytes, adjust size of this if other key types are supported.
; Currently ONLY AlgorithmIdentifier int(12) - Ed25519 is supported.
; oid and [ algorithm: oid, parameters: bytes ] are not supported by Plutus.
AlgorithmIdentifier = int / ~oid / [ algorithm: ~oid, parameters: bytes ]

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

CardanoPublicKey = ( attributeType: &cardanoKeyTypes proof: bool, attributeValue: bytes .size (28..28) )
Attribute = ( attributeType: int, attributeValue: text )
// ( attributeType: ~oid, attributeValue: bytes )

cardanoKeyTypes = (
paymentKeyHash: 0,
stakeKeyHash: 1,
drepVerificationKeyHash: 2,
ccColdVerificationKeyHash: 3,
ccHotVerificationKeyHash: 4,
)
subjectPublicKey = bytes .size (32..32) ; Ed25519 public key stored in bytes, adjust size if other key types are supported.

; Plutus will need to convert the Unix epoch timestamp to the nearest slot number
; For ~time, Plutus will need to convert the Unix epoch timestamp to the nearest slot number
; validityNotBefore rounds up to the next Slot after that time.
; validityNotAfter rounds down to the next Slot before that time.
Time = ( ~time / null )

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

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

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

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

Extension = (
( extensionID: int, extensionValue: any )
// ( extensionID: ~oid, ? critical: true, extensionValue: bytes )
// ( extensionID: pen, ? critical: true, extensionValue: bytes )
( extensionID: int, extensionValue: any )
// ( extensionID: ~oid, ? critical: true, extensionValue: bytes )
)

SubjectAltName = GeneralNames / text
GeneralNames = [ + GeneralName ]
GeneralName = ( GeneralNameType: int, GeneralNameValue: any )
4 changes: 2 additions & 2 deletions rust/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ code-format:

# Lint the rust code
code-lint:
cargo lintfix
cargo lint
cargo lintfix -r
cargo lint -r

# Pre Push Checks
pre-push: sync-cfg code-format code-lint license-check
Expand Down
6 changes: 3 additions & 3 deletions rust/c509-certificate/examples/cli/data/cert_sample_1.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"self_signed": true,
"c509_certificate_type": 0,
"c509_certificate_type": 2,
"certificate_serial_number": 128269,
"issuer_signature_algorithm": null,
"issuer": [
{
"oid": "2.5.4.3",
Expand All @@ -24,6 +25,5 @@
"value": { "int": 1 },
"critical": false
}
],
"issuer_signature_algorithm": null
]
}
115 changes: 67 additions & 48 deletions rust/c509-certificate/examples/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ use std::{

use asn1_rs::{oid, Oid};
use c509_certificate::{
attributes::attribute::Attribute,
big_uint::UnwrappedBigUint,
cert_tbs::TbsCert,
extensions::Extensions,
issuer_sig_algo::IssuerSignatureAlgorithm,
name::{rdn::RelativeDistinguishedName, Name, NameValue},
name::{Name, NameValue},
signing::{PrivateKey, PublicKey},
subject_pub_key_algo::SubjectPubKeyAlgorithm,
tbs_cert::TbsCert,
time::Time,
};
use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -97,22 +98,25 @@ impl Cli {
struct C509Json {
/// Indicate whether the certificate is self-signed.
self_signed: bool,
/// Optional certificate type, if not provided, set to 0 as self-signed.
/// Optional certificate type, if not provided, set to 2 as self-signed.
certificate_type: Option<u8>,
/// Optional serial number of the certificate,
/// if not provided, a random number will be generated.
serial_number: Option<UnwrappedBigUint>,
/// Optional issuer signature algorithm of the certificate,
/// if not provided, set to Ed25519.
issuer_signature_algorithm: Option<IssuerSignatureAlgorithm>,
/// Optional issuer of the certificate,
/// if not provided, issuer is the same as subject.
issuer: Option<RelativeDistinguishedName>,
issuer: Option<Vec<Attribute>>,
/// Optional validity not before date,
/// if not provided, set to current time.
validity_not_before: Option<String>,
/// Optional validity not after date,
/// if not provided, set to no expire date 9999-12-31T23:59:59+00:00.
validity_not_after: Option<String>,
/// Relative distinguished name of the subject.
subject: RelativeDistinguishedName,
/// Attributes of the subject.
subject: Vec<Attribute>,
/// Optional subject public key algorithm of the certificate,
/// if not provided, set to Ed25519.
subject_public_key_algorithm: Option<SubjectPubKeyAlgorithm>,
Expand All @@ -121,9 +125,6 @@ struct C509Json {
subject_public_key: String,
/// Extensions of the certificate.
extensions: Extensions,
/// Optional issuer signature algorithm of the certificate,
/// if not provided, set to Ed25519.
issuer_signature_algorithm: Option<IssuerSignatureAlgorithm>,
/// Optional issuer signature value of the certificate.
#[serde(skip_deserializing)]
issuer_signature_value: Option<Vec<u8>>,
Expand All @@ -133,9 +134,9 @@ struct C509Json {
const ED25519: (Oid, Option<String>) = (oid!(1.3.101 .112), None);

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

// -------------------generate-----------------------

Expand All @@ -159,7 +160,12 @@ fn generate(

// Parse validity dates or use defaults
// Now for not_before date
let not_before = parse_or_default_date(c509_json.validity_not_before, Utc::now().timestamp())?;
let now_timestamp: u64 = Utc::now()
.timestamp()
.try_into()
.map_err(|_| anyhow::anyhow!("Current timestamp is invalid"))?;

let not_before = parse_or_default_date(c509_json.validity_not_before, now_timestamp)?;
// Default as expire date for not_after
// Expire date = 9999-12-31T23:59:59+00:00 as mention in the C509 document
let not_after = parse_or_default_date(
Expand All @@ -175,18 +181,18 @@ fn generate(
let tbs = TbsCert::new(
c509_json.certificate_type.unwrap_or(SELF_SIGNED_INT),
serial_number,
Name::new(NameValue::RelativeDistinguishedName(issuer)),
c509_json
.issuer_signature_algorithm
.unwrap_or(IssuerSignatureAlgorithm::new(key_type.0.clone(), ED25519.1)),
Some(Name::new(NameValue::Attribute(issuer))),
Time::new(not_before),
Time::new(not_after),
Name::new(NameValue::RelativeDistinguishedName(c509_json.subject)),
Name::new(NameValue::Attribute(c509_json.subject)),
c509_json
.subject_public_key_algorithm
.unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0.clone(), key_type.1)),
.unwrap_or(SubjectPubKeyAlgorithm::new(key_type.0, key_type.1)),
public_key.to_bytes(),
c509_json.extensions.clone(),
c509_json
.issuer_signature_algorithm
.unwrap_or(IssuerSignatureAlgorithm::new(key_type.0, ED25519.1)),
);

let cert = c509_certificate::generate(&tbs, private_key)?;
Expand All @@ -213,9 +219,8 @@ fn write_to_output_file(output: PathBuf, data: &[u8]) -> anyhow::Result<()> {
/// If self-signed is true, issuer is the same as subject.
/// Otherwise, issuer must be present.
fn determine_issuer(
self_signed: bool, issuer: Option<RelativeDistinguishedName>,
subject: RelativeDistinguishedName,
) -> anyhow::Result<RelativeDistinguishedName> {
self_signed: bool, issuer: Option<Vec<Attribute>>, subject: Vec<Attribute>,
) -> anyhow::Result<Vec<Attribute>> {
if self_signed {
Ok(subject)
} else {
Expand All @@ -229,7 +234,7 @@ fn validate_certificate_type(
) -> anyhow::Result<()> {
if self_signed && certificate_type.unwrap_or(SELF_SIGNED_INT) != SELF_SIGNED_INT {
return Err(anyhow::anyhow!(
"Certificate type must be 0 if self-signed is true"
"Certificate type must be {SELF_SIGNED_INT} if self-signed is true"
));
}
Ok(())
Expand All @@ -249,13 +254,17 @@ fn get_key_type(key_type: &Option<String>) -> anyhow::Result<(Oid<'static>, Opti
}
}

/// Parse date string to i64.
fn parse_or_default_date(date_option: Option<String>, default: i64) -> Result<i64, anyhow::Error> {
/// Parse date string to u64.
fn parse_or_default_date(date_option: Option<String>, default: u64) -> Result<u64, anyhow::Error> {
match date_option {
Some(date) => {
DateTime::parse_from_rfc3339(&date)
.map(|dt| dt.timestamp())
.map_err(|e| anyhow::anyhow!(format!("Failed to parse date {date}: {e}",)))
.map(|dt| {
dt.timestamp()
.try_into()
.map_err(|_| anyhow::anyhow!("Timestamp is invalid"))
})?
.map_err(|e| anyhow::anyhow!("Failed to parse date {date}: {e}"))
},
None => Ok(default),
}
Expand Down Expand Up @@ -288,22 +297,26 @@ fn decode(file: &PathBuf, output: Option<PathBuf>) -> anyhow::Result<()> {
let mut d = minicbor::Decoder::new(&cert);
let c509 = c509_certificate::c509::C509::decode(&mut d, &mut ())?;

let tbs_cert = c509.get_tbs_cert();
let is_self_signed = tbs_cert.get_c509_certificate_type() == SELF_SIGNED_INT;
let tbs_cert = c509.tbs_cert();
let is_self_signed = tbs_cert.c509_certificate_type() == SELF_SIGNED_INT;
let c509_json = C509Json {
self_signed: is_self_signed,
certificate_type: Some(tbs_cert.get_c509_certificate_type()),
serial_number: Some(tbs_cert.get_certificate_serial_number().clone()),
issuer: Some(extract_relative_distinguished_name(tbs_cert.get_issuer())?),
validity_not_before: Some(time_to_string(tbs_cert.get_validity_not_before().to_i64())?),
validity_not_after: Some(time_to_string(tbs_cert.get_validity_not_after().to_i64())?),
subject: extract_relative_distinguished_name(tbs_cert.get_subject())?,
subject_public_key_algorithm: Some(tbs_cert.get_subject_public_key_algorithm().clone()),
certificate_type: Some(tbs_cert.c509_certificate_type()),
serial_number: Some(tbs_cert.certificate_serial_number().clone()),
issuer_signature_algorithm: Some(tbs_cert.issuer_signature_algorithm().clone()),
issuer: Some(extract_attributes(tbs_cert.issuer())?),
validity_not_before: Some(time_to_string(
tbs_cert.validity_not_before().clone().into(),
)?),
validity_not_after: Some(time_to_string(
tbs_cert.validity_not_after().clone().into(),
)?),
subject: extract_attributes(tbs_cert.subject())?,
subject_public_key_algorithm: Some(tbs_cert.subject_public_key_algorithm().clone()),
// Return a hex formation of the public key
subject_public_key: tbs_cert.get_subject_public_key().encode_hex(),
extensions: tbs_cert.get_extensions().clone(),
issuer_signature_algorithm: Some(tbs_cert.get_issuer_signature_algorithm().clone()),
issuer_signature_value: c509.get_issuer_signature_value().clone(),
subject_public_key: tbs_cert.subject_public_key().encode_hex(),
extensions: tbs_cert.extensions().clone(),
issuer_signature_value: c509.issuer_signature_value().clone(),
};

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

/// Extract a `RelativeDistinguishedName` from a `Name`.
fn extract_relative_distinguished_name(name: &Name) -> anyhow::Result<RelativeDistinguishedName> {
match name.get_value() {
NameValue::RelativeDistinguishedName(rdn) => Ok(rdn.clone()),
_ => Err(anyhow::anyhow!("Expected RelativeDistinguishedName")),
/// Extract a `Attributes` from a `Name`.
fn extract_attributes(name: &Name) -> anyhow::Result<Vec<Attribute>> {
match name.value() {
NameValue::Attribute(attrs) => Ok(attrs.clone()),
_ => Err(anyhow::anyhow!("Expected Attributes")),
}
}

/// Convert time in i64 to string.
fn time_to_string(time: i64) -> anyhow::Result<String> {
let datetime =
DateTime::from_timestamp(time, 0).ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?;
fn time_to_string(time: u64) -> anyhow::Result<String> {
// Attempt to convert the timestamp and handle errors if they occur
let timestamp: i64 = time
.try_into()
.map_err(|e| anyhow::anyhow!("Failed to convert time: {:?}", e))?;

// Convert the timestamp to a DateTime and handle any potential errors
let datetime = DateTime::from_timestamp(timestamp, 0)
.ok_or_else(|| anyhow::anyhow!("Invalid timestamp"))?;
Ok(datetime.to_rfc3339())
}

Expand Down
Loading