Giter Site home page Giter Site logo

rcgen's Introduction

rcgen

docs crates.io dependency status

Simple Rust library to generate X.509 certificates.

use rcgen::generate_simple_self_signed;
let subject_alt_names = vec!["hello.world.example".to_string(),
	"localhost".to_string()];

let cert = generate_simple_self_signed(subject_alt_names).unwrap();
// The certificate is now valid for localhost and the domain "hello.world.example"
println!("{}", cert.serialize_pem().unwrap());
println!("{}", cert.serialize_private_key_pem());

Trying it out with openssl

You can do this:

cargo run
openssl x509 -in certs/cert.pem -text -noout

For debugging, pasting the PEM formatted text to this service is very useful.

Trying it out with quinn

You can use rcgen together with the quinn crate. The whole set of commands is:

cargo run
cd ../quinn
cargo run --example server -- --cert ../rcgen/certs/cert.pem --key ../rcgen/certs/key.pem ./
cargo run --example client -- --ca ../rcgen/certs/cert.der https://localhost:4433/README.md

MSRV

The MSRV policy is to strive for supporting 7-month old Rust versions.

License

This crate is distributed under the terms of both the MIT license and the Apache License (Version 2.0), at your option.

See LICENSE for details.

License of your contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

rcgen's People

Contributors

alvenix avatar andrenth avatar biagiofesta avatar brocaar avatar corrideat avatar cpu avatar daxpedda avatar dependabot[bot] avatar djc avatar doraneko94 avatar est31 avatar frjonsen avatar fzgregor avatar g2p avatar iamjpotts avatar jean-airoldie avatar matze avatar nickcao avatar nthuemmel avatar omjadas avatar tbro avatar thomaseizinger avatar thomastaylor312 avatar tindzk avatar trevor-crypto avatar tshepang avatar tudyx avatar uglyoldbob avatar will-low avatar zurborg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

rcgen's Issues

API is not very clear about how to sign one `Certificate` with another one.

The only mention of signing new certificates I see is serialize_*_with_signer functions.

I am expected to serialize, then immediately deserialize certificate even though there is no intention to save it to file or transmit.

Documentation may be more clear about why signing and serialization are one step. I expected there be something like rcgen::Certificate::from_params_with_signer(params: CertificateParams, ca: &Certificate), so I can sign certificate, then serialize it if needed (or use somehow directly).

Question Generating Certificate when we have key?

I want to generate self signed certificate for mitm proxy. I used openssl to generate key

openssl genrsa -out cert.key 2048
openssl req -new -x509 -key cert.key -out cert.crt

But as it is mitm proxy I need to adjust certificate subject line ON feild according to request. If request comes for xyz.com i need to change ON=xyz.com and if it comes for jsonip.com I need to change certificate subject to ON=jsonip.com so I wont see tls error.

How do generate x509 key in such case where i just need to change ON and nothing else?

I basically wanted to create root cert and key using open ssl command add it to trusted root certificate . And using rcgen wanted to generate x509 certificate with the change of subject line having OU=xyz.com.

I hope there is easy way to accomplish this with rcgen.
Thanks.

Documentation improvement: mention the "x509-parser" feature flag

I wasted a lot of time trying to figure out that I needed to enable the optional extra "x509-parser" feature flag, in order to use the CertificateParams::from_ca_cert_{pem,der} functions, which I found from the API docs on docs.rs. This is not mentioned in the documentation or the readme. docs.rs API documentation is generated with the feature enabled, giving the impression that the functions are available.

It would be nice if a mention about this is added somewhere relevant.

Open to a formatting with rustfmt?

Formatting with cargo fmt has been widely adopted by now. I was wondering if you were open to introduce formatting with rustfmt?

You could introduce a .rustfmt.toml, which almost maintains the current formatting config.

DateTime nanoseconds not being stripped

It appears that write_dt_utc_or_generalized() is not stripping nanoseconds for chrono::DateTime<Utc>.

Simple example is as follows:

// create certificate_params
...
let current_time = Utc::now();
// set not_before and not_after
...
// generate certificate
let cert = Certificate::from_params(certificate_params)?;

libs.rs:

fn write_dt_utc_or_generalized(writer: DERWriter, dt: &DateTime<Utc>) -> Result<(), RcgenError> {
    // RFC 5280 requires CAs to write certificate validity dates
    // below 2050 as UTCTime, and anything starting from 2050
    // as GeneralizedTime [1]. The RFC doesn't say anything
    // about dates before 1950, but as UTCTime can't represent
    // them, we have to use GeneralizedTime if we want to or not.
    // [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5
    if (1950..2050).contains(&dt.year()) {
        let ut = UTCTime::from_datetime::<Utc>(dt); // fails here
        writer.write_utctime(&ut);
    } else {
        let gt = dt_to_generalized(dt)?;
        writer.write_generalized_time(&gt);
    }
    Ok(())
}

Add extensionRequest to Serialized Certificate Signing Requests

Currently users of rcgen can set many fields in CertificateParams that will be silently ignored when serializing a CSR.

One example is:

use rcgen;

let mut params = CertificateParams::default();

// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
params.key_usages.push(KeyUsagePurpose::DigitalSignature);
params.key_usages.push(KeyUsagePurpose::KeyEncipherment);
params.key_usages.push(KeyUsagePurpose::KeyAgreement);

// ServerAuth may be consistent with DigitalSignature, KeyEncipherment, and KeyAgreement per rfc5280 #4.2.1.12
// https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12
params
    .extended_key_usages
    .push(ExtendedKeyUsagePurpose::ServerAuth);

params.key_pair = Some(KeyPair::from_der(pkcs8_bytes)?);

let certificate = Certificate::from_params(params)?;

certificate.serialize_request_pem()?

In this example the key_usages and extended_key_usages fields will not be present in the serialized CSR.

Per rfc2985 5.4.2 the extensionRequest attribute type may be used to carry information about certificate extensions the requester wishes to be included in a certificate.

It seems to me that subject_alt_names, key_usages, extended_key_usages, name_constraints, custom_extensions, and the use_authority_key_identifier_extension fields are all valid things to include in the extensionRequest attribute. Currently it seems like subject_alt_names is the only thing that is included.

Would you be open to merging a PR that adds key_usages, extended_key_usages, name_constraints, custom_extensions, and the Authority Key Identifier extensions to the extension request section during CSR serialization?

Certificate imported via from_ca_cert_* serializes incorrectly

Importing a Certificate via from_ca_cert_pem or from_ca_cert_der then running Certificate::serialize_der on it yields invalid certificates, at least according to webpki (BadDer).

My work around was to keep a copy of the imported certificate as a backup and yield that backup when the certificate is requested.

Add methods to inspect DistinguishedName

Right now DistinguishedName can be written, but not read from outside the crate. Adding methods to get and iterate DN elements would be helpful.

  • Add a method like pub fn get(&self, ty: DnType) -> Option<&str>
  • Make method iter - and the corresponding Iterator - public

Support multiple DnValue per key in DistinguishedName

I need to generate a certificate with a subject line containing multiple OU key-value pairs:

Subject: OU=certtype:instance, OU=compartment:compartment, OU=instance:instance

The problem is subsequent calls to DistinguishedName::push overwrite the previous value:

params.distinguished_name.push(DnType::OrganizationalUnitName, format!("certtype:{}", certtype));
params.distinguished_name.push(DnType::OrganizationalUnitName, format!("compartment:{}", compartment));
params.distinguished_name.push(DnType::OrganizationalUnitName, format!("instance:{}", instance));

This leaves me with a subject containing only the last value:

Subject: OU=instance:instance

Problem creating self signed cert with ECDSA algorithm and using as client identity in native-tls

Hi everyone,

I am having trouble creating a self signed certificate with ECDSA that will be usable (on macOS) with native-tls as a client identity.

This is the code I have so far:

fn get_identity() -> Result<native_tls::Identity, String>
    let mut dn = DistinguishedName::new();
    dn.push(rcgen::DnType::OrganizationName, "Demo");
    dn.push(rcgen::DnType::CountryName, "DE");
    dn.push(rcgen::DnType::CommonName, "Demo");
    
    let mut cert_params = CertificateParams::default();
    cert_params.distinguished_name = dn;
    cert_params.serial_number = Option::Some(1);
    cert_params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
    cert_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);

    let certificate = match Certificate::from_params(cert_params) {
        Ok(certificate) => certificate,
        Err(e) => return Err(e.to_string()),
    };

    let certificate = match Certificate::from_params(cert_params) {
        Ok(certificate) => certificate,
        Err(e) => return Err(e.to_string()),
    };

    let cert = match certificate.serialize_pem() {
        Ok(cert) => cert,
        Err(err) => return Err(format!("Error in serializing the cert pem: {}", err)),
    };
    let key = certificate.serialize_private_key_pem();
    
    match native_tls::Identity::from_pkcs8(cert.as_bytes(), key.as_bytes()) {
        Ok(identity) => return Ok(identity),
        Err(err) => {
            println!("Error in creating identity: {}", err);
        },
    };
}

The error I am getting from macOS security framework is:

Error in creating identity: Unknown format in import.

I got as far as identifying the issue being with the private key.

Is there anything I am doing completely wrong?

Thanks
Andreas

KeyPair::is_compatible yields wrong result for different ECDSA key types

KeyPair::is_compatible() yields true when:

  • The Keypair is of type P384 and the given alg is PKCS_ECDSA_P256_SHA256
  • The Keypair is of type P256 and the given alg is PKCS_ECDSA_P384_SHA384

This has the consequence that when CertificateParams have such a combination of algorithm & KeyPair a Certificate will be generated without error, but will have a mismatch between public key type and public key data, rendering it ill-formed.

IP Address SANs are invalid

Hi,
The IP Address SAN is being encoded as an octet string of length 8 when it should be of length 4. This first four bytes are [48, 6, 135, 4] (decimal), irrelevant of the actual IP address. The last four bytes are the actual IP address. This seems to be related to #25, but that was fixed more than two years ago.

I noticed that the CertificateParams::write_extension method seems to be adding the [135, 4] bytes to the octet string. I've done very little work with ASN.1, so I might be completely missing something, especially considering no one else has recently had this issue.

serialize_der() regenerates the certificate

I am using rcgen to generate a certificate, then I'm sharing the fingerprint of the certificate with a peer, and then I'm using a library to communicate with that peer. The library requires me to provide the certificate & private key in PEM format.

I've been debugging for a while why the fingerprint I generate, like this:

let fingerprint = ring::digest::digest(&ring::digest::SHA256, &cert.serialize_der()?);

... does not match the fingerprint the peer expects to receive, nor the fingerprint OpenSSL reports when I supply it with the PEM-encoded certificate obtained with serialize_pem().

After reading the discussion in #28, and looking at the code, I think I understand what's going on โ€”ย serialize_der() and serialize_pem() are not actually just serializing; they are in fact generating the certificate each time they are called, so the random components are different between the DER serialization and the PEM serialization.

Ideally the certificate would be generated when the Certificate struct is created, so that it can be repeatedly serialized โ€” perhaps into different formats โ€”ย without regenerating it each time.

If that's not possible, the functions should either be renamed or at least the mis-naming documented clearly to save the time of anyone else who encounters this.

Right now, from_params() is documented as "Generates a new certificate" and serialize_der() is documented as "Serializes the certificate to the binary DER format", when in reality from_params() just stores the params (and does keygen if needed), and serialize_der() etc actually do the certificate generation.

Ability to generate a certificate from (remote) raw private key

Currently there is no way to generate a full X.509 certificate from a raw private and public key.
Additionally support for a "remote" raw private key would be amazing too, with remote meaning that no direct access is available, but a callback to signing is.

At the moment my ad-hoc solution is to copy some parts of rcgen.

I would propose to add an enum of sorts to rcgen::CertificateParams::key_pair that has the ability to take in a Box to a trait similar to rustls::sign::SigningKey and the public key in it's raw form.

Intending to make a PR myself if it's decided that this falls into the scope of rcgen.

My use case:
I'm currently writing a library that enables cross-platform access to the OS certificate vault and store. This includes generation, storage and some level of editing.

The goal is to be able to generate a key pair inside the OS vault and use that for example in rustls. When done that way, there is no access to the private key directly. This works great with rustls because it offers SigningKey, which can call the OS vault signing function to sign a message without direct access to the private key.

The missing part is X.509 certificate generation. All three OS's (Linux, MacOS and Windows) generate only raw keys. Which is a great option for the future: rustls/rustls#423.
But right now and in some other use cases, for example using it on a server with a HSM for example, you need to generate a X.509 certificate from the already generated raw keys in addition to the private key not being directly accessible.

At the moment the usual solution is to use tools or OS API's to generate certificates, it would be great to allow that to be replaced by rcgen.

Can you produce a set of prebuilt binaries?

I think it would be really neat if you offered a bunch of prebuilt binaries for OSX, Linux, Windows so that you don't need OpenSSL on those systems in order to generate certificates.

Since you're already using travis, you could have a peek at how I do it for some inspiration. :)

Add tests

For tests, we should use the openssl crate to ensure that the certs are formatted correctly.

[Question] modifying distinguished_name

I was able to create CertificateParam from existing certificate. But I only want to change OU field of certificate.

What i found is internally it is stored in CerficateParam's distinguished_name field.

The unfortunate situation is DistinguishedName has two field which are not public . And DistinguishedName only provides new get push and iter which doesn't give ability to modify the internal value inside hashmap?

Here was my initial thought .

 let mut param =
        CertificateParams::from_ca_cert_pem(&cert_buf[..], key_pair).expect("Invalid pem key");

       if let Some(x) = param.distinguished_name.entries.get_mut(rcgen::DnType::CustomDnType(u64::from_ne_bytes(b"OU"))) {
         *x = "yahoo.com"; // yahoo.com is what i want to set of course dynamically
     }

But unfortunately param.distinguished_name.entries is private :(

Let me know if i am doing something wrong?

Thanks :)

Make ring optional

It would be nice to take advantage of the serialization part of the library without being tied specifically to ring. I believe RemoteKeypair could be leveraged to provide your own implementation of signing (perhaps with the Openssl crate)?

Extend public key pair interface

It would be great to have additional methods to interact with a specified or generated keypair.
Right now, I am missing two features:

  • Exporting the public key, for both KeyPair and Certificate, in DER format (example: serialize_public_key_der(&self) -> Vec<u8>)
  • Having the option to check KeyPair compatibility with a given algorithm manually (making KeyPair::is_compatible(...) public should do the trick)

Explicitly zero a `KeyPair` after use.

I'm missing a way to zero a KeyPair after use.

Using the crate zeroise, one can zeroize the string return by Certificate::serialize_private_key_pem but not the KeyPair internally hold by that Certificate,
because some Into<Zeroizing> trait implementation is missing.

Could rcgen provide such zeroize feature? With some explicit call or implicitly in Drop?

Separate keypair type from SignatureAlgorithm

Right now there is no way to map a deserialized KeyPair back to a usable SignatureAlgorithm (apart from using is_compatible() repeatedly for all known algorithms, and that is bugged, see #18 ).

It would improve usability a lot - and also achieve a proper separation of concerns - by having a separate, exposed enum for the type of KeyPair, having variants such as RSA(bitlen), EC_P256, EC_P384 and Ed25519 and being able to query this type for any given KeyPair instance. Library users may then choose an appropriate compatible SignatureAlgorithm.

Loading CA cert and using for signing doesn't match original certificate

I'm trying to develop a P2P application where clients can register with a server, which will sign their certificates, so that they can later talk to each other without the involvement of the server.

I've been trying to load the CA cert using CertificateParams::from_ca_cert_pem and sign the client certs with Certificate::serialize_der_with_signer. However the "issuer" attached to the resulting signed cert doesn't match the original cert (specifically the "subject" is 1 byte different).

I don't really know enough about TLS to figure out why this is happening, so let me know if I can provide any additional details.

My (testing) code looks like this:

    let ca_key = rcgen::KeyPair::from_der(debug_certs::CA_KEY_PK8).unwrap();
    let params = rcgen::CertificateParams::from_ca_cert_pem(
        debug_certs::CA_CERT,
        ca_key,
    )
    .unwrap();
    let ca_cert = rcgen::Certificate::from_params(params).unwrap();
    let gen_cert = rcgen::generate_simple_self_signed(vec![hostname]).unwrap();
    gen_cert.serialize_der_with_signer(&ca_cert).unwrap();

The issuer "subject" lines I get during certificate verification look like this (as byte arrays, note byte index 9)

Certificate "Issuer":

[49, 11, 48, 9, 6, 3, 85, 4, 6, 12, 2, 85, 83, 49, 17, 48, 15, 6, 3, 85, 4, 8, 12, 8, 86, 105, 114, 103, 105, 110, 105, 97, 49, 33, 48, 31, 6, 3, 85, 4, 10, 12, 24, 73, 110, 116, 101, 114, 110, 101, 116, 32, 87, 105, 100, 103, 105, 116, 115, 32, 80, 116, 121, 32, 76, 116, 100]

Trust Anchor "Subject":

[49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 17, 48, 15, 6, 3, 85, 4, 8, 12, 8, 86, 105, 114, 103, 105, 110, 105, 97, 49, 33, 48, 31, 6, 3, 85, 4, 10, 12, 24, 73, 110, 116, 101, 114, 110, 101, 116, 32, 87, 105, 100, 103, 105, 116, 115, 32, 80, 116, 121, 32, 76, 116, 100]

debug-game-cert.pem.txt
example_signed_cert.pem.txt

Can we lower the MSRV?

0.9 raised the MSRV to 1.56 by virtue of setting the edition to 2021. IMO it would be nice to avoid that for a while longer; for Quinn, for example, we have a policy of sticking to 6-month old versions for the MSRV.

Can we create CSRs?

Hey there!

I'm currently playing around with your package. I try to create a CA that can receive a CSR and sign the CSR. While I saw that I cannot use RSA (because of ring as far as I understand), it seems that I cannot create a CSR in code as well, since I cannot extract the public key from a KeyPair.

Do you have any advice on how I can manage to create such a PKI, or am I bound to other languages such as go?

Nevertheless, nice work in this lib!

Regards

Proposal: add `mkcert` to Cargo.toml keywords.

This crate is hard to find on Crates.io, as it lacks categories and keywords.

I suggest to add them. Apart from obvious keywords like certificate, tls, ca and X.509, I suggest to also include mkcert keyword. It is a popular CLI tool that does some things that this library also does, so it may serve as a way to discover this library.

Ed25519KeyPair::from_pkcs8_maybe_unchecked or from_pkcs8 ?

There are two functions to parse Ed25519 keys in ring, Ed25519KeyPair::from_pkcs8 and from_pkcs8_maybe_unchecked. Currently, we are using the former but it requires the keys to be in PKCS#8 v2 format. The command openssl genpkey -algorithm ED25519 does not generate such keys, so they are being rejected. If we change the parsing function to from_pkcs8_maybe_unchecked, they are being accepted.

Right now I'm wondering how to make openssl generate PKCS#8 v2 keys or whether there is a way convert v1 keys to v2 ones. If there is a way to do the conversion with openssl cli tools, I'm inclined to keeping from_pkcs8, otherwise I think we should switch to from_pkcs8_maybe_unchecked.

Raw bytes expected instead for RemoteKeyPair#public_key

It is stated in the doc that the public_key should returned in DER format
https://docs.rs/rcgen/0.9.2/rcgen/trait.RemoteKeyPair.html#tymethod.public_key

However it should be the raw bytes of the public key as this is what provided from ring
https://docs.rs/rcgen/0.9.2/src/rcgen/lib.rs.html#1781
I can't find in the docs of thering library but here it says the output from ring is in raw format
https://docs.rs/rcgen/0.9.2/rcgen/struct.KeyPair.html#method.public_key_raw

How to consistently hash a certificate

Hi! I don't know if this is the best place for questions. If not feel free to close this.

I am implementing TLS authentication between two peers. On each peer I generate a Certificate and serialize it to disk with cert.serialize_private_key_pem().

Later I exchange peers' certificates cert.serialize_der() and then use rustls for establishing TLS session.

Everything works out well. The problem I am having is that I want to generate a hash (unique peer ID) from public certificate and keep it the same for each peer on each run. I've noticed that every time I call cert.serialize_der() it generates a slightly different output.

It seems that underlying key pair's public key remains the same so I can consistently generate a hash on each peer but then when TLS session is established I would like each peer to generate hash again from rustls::Session::get_peer_certificates().

What am I missing? What should I use as an input for hash generation? Is KeyPair::public_key_der() a good candidate? If yes, how do get it from rustls::Session?

My issues are most likely due to the lack of knowledge about certificates and encryption in general.

Botan needlessly disabled on macOS

Just an FYI the logic in #42 is incorrect, Botan supports macOS (aka x86_64-apple-darwin) just fine, and has for years. It does not support the old MacOS "Classic" which nobody uses anymore. I think the confusion came about because of the recent rebranding of OS X to macOS, plus the fact that the docs even bothered mentioning Classic, which has been dead for 20 years. I've amended Botan's documentation so the situation is more clear.

IpAddress SANs are invalid

Not super sure why this is writing CIDRs?

The OID description expects only an octet string under the tag IPAddress:

IPAddress	[7] OCTET STRING,

and OpenSSL marks rcgen-generated IP fields as invalid:

X509v3 Subject Alternative Name:
    IP Address:<invalid>        

Generating IPAddress SANs with OpenSSL makes [u8; 4] and [u8; 16] for IP v4 and v6 respectively, and not CIDRs with the mask.

TaggedDerValue {
    tag: Tag {
        tag_class: ContextSpecific,
        tag_number: 7,
    },
    pcbit: Primitive,
    value: [
        1,
        2,
        3,
        4,
    ],
}

Basic Constraints certificate extension

For Basic Constraints certificate extension currently rcgen support two options:

pub enum IsCa {
    SelfSignedOnly,
    Ca(BasicConstraints)
}

And if we look into spec we will see exactly these two options:

id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }

   BasicConstraints ::= SEQUENCE {
        cA                      BOOLEAN DEFAULT FALSE,
        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
and extension identifies two aspects:

Subject type: is the certificate a CA certificate or it is an end entity certificate;
[Optionally] How many CAs are allowed in the chain below current CA certificate. This setting has no meaning for end entity certificates.

And for sure Ca(x) case generate something like:

SubjectType=CA
Path length Constraint = x

But in case of SelfSignedOnly it didn't generate anything.

Empty

Despite spec said: "If Basic Constraints extension is not included in certificate, it is automatically treated as end entity certificate."

Is it possible to force writing this field?

SubjectType=End Entity
Path length Constraint = None

RcgenError variants for "third-party" failures in RemoteKeyPair

I am currently investigating this crate for potential use within the Parsec project, since it provides the ideal facilities for creating certs and CSRs from a private key that is managed in hardware. The RemoteKeyPair trait provides a nice extension point where I can call out to key functions that are implemented in Parsec (which in turn can then be back-ended into a variety of TPMs, HSMs or secure elements).

One small interface wrinkle is in the error handling. The RemoteKeyPair::sign() function is contracted to return RcgenError on a failure. But this is an enum with a fixed set of variants, and none of the existing variants is directly applicable if the signing function fails in a third-party component. It would be good to have a RcgenError::RemoteKeyError or something similar to flag when the remote implementation has encountered an error that is not explicitly modelled in the existing variants.

csr verification with openssl gives error for ed25519 certificates

In tests/openssl.rs, invocation of the verify_csr(&cert); function is commented out for ed25519 certificates because it fails. I'm not sure what the problem is.

This is the output if you remove the //:

---- test_openssl_25519_given stdout ----
-----BEGIN CERTIFICATE-----
MIIBLzCB4qADAgECAgEqMAUGAytlcDAsMSowFgYDVQQKDA9DcmFiIHdpZGdpdHMg
U0UwEAYDVQQDDAlNYXN0ZXIgQ0EwIhgPMTk3NTAxMDEwMDAwMDBaGA80MDk2MDEw
MTAwMDAwMFowLDEqMBYGA1UECgwPQ3JhYiB3aWRnaXRzIFNFMBAGA1UEAwwJTWFz
dGVyIENBMCowBQYDK2VwAyEA67x/8fDcfbp7bLnOhE/nHt8Oz0ri2PAS1nB3Vwxr
5ECjJTAjMCEGA1UdEQQaMBiCC2NyYWJzLmNyYWJzgglsb2NhbGhvc3QwBQYDK2Vw
A0EANUf5PpvKy0FIAHybycyZZViPYXndWjFMPSLMH9qNmKo3VnsCwjWlboetGT6i
j3popEMaJ8nhDP4AAqo+/c/vDA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE REQUEST-----
MIHiMIGVAgEAMC4xGDAWBgNVBAoMD0NyYWIgd2lkZ2l0cyBTRTESMBAGA1UEAwwJ
TWFzdGVyIENBMCowBQYDK2VwAyEA67x/8fDcfbp7bLnOhE/nHt8Oz0ri2PAS1nB3
Vwxr5ECgNDAyBgkqhkiG9w0BCQ4xJTAjMCEGA1UdEQQaMBiCC2NyYWJzLmNyYWJz
gglsb2NhbGhvc3QwBQYDK2VwA0EAbXjjM1CwFLPPCMRDNtt0SnVLvD7gv70Hj9TA
UWb1oFnKmIu9qQOXOW7xb5YHGcwPyo0zTpBU5vukDFkraojhDg==
-----END CERTIFICATE REQUEST-----

thread 'test_openssl_25519_given' panicked at 'called `Result::unwrap()` on an `Err` value: ErrorStack([Error { code: 218529960, library: "asn1 encoding routines", function: "asn1_check_tlen", reason: "wrong tag", file: "../crypto/asn1/tasn_dec.c", line: 1130 }, Error { code: 218546234, library: "asn1 encoding routines", function: "asn1_d2i_ex_primitive", reason: "nested asn1 error", file: "../crypto/asn1/tasn_dec.c", line: 694 }, Error { code: 218640442, library: "asn1 encoding routines", function: "asn1_template_noexp_d2i", reason: "nested asn1 error", file: "../crypto/asn1/tasn_dec.c", line: 627, data: "Field=privateKey, Type=EC_PRIVATEKEY" }, Error { code: 269033488, library: "elliptic curve routines", function: "d2i_ECPrivateKey", reason: "EC lib", file: "../crypto/ec/ec_asn1.c", line: 899 }, Error { code: 269344910, library: "elliptic curve routines", function: "old_ec_priv_decode", reason: "decode error", file: "../crypto/ec/ec_ameth.c", line: 447 }, Error { code: 218529960, library: "asn1 encoding routines", function: "asn1_check_tlen", reason: "wrong tag", file: "../crypto/asn1/tasn_dec.c", line: 1130 }, Error { code: 218640442, library: "asn1 encoding routines", function: "asn1_template_noexp_d2i", reason: "nested asn1 error", file: "../crypto/asn1/tasn_dec.c", line: 553, data: "Field=attributes, Type=PKCS8_PRIV_KEY_INFO" }])', src/libcore/result.rs:997:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

cc @djc

format error in certificate's notBefore field

First of all, thanks for this great little library. Works as advertised and is very straight-forward.

I hit a bit of a snag after attempting to generate and use a CA+CA signed certificate (code below). Verification fails on macOS (LibreSSL 2.8.3) with a format error in certificate's notBefore field. Interestingly, it appears to work just fine on Linux with the same certificates.

Some digging revealed that this probably has something to do with the date format used by rcgen. See e.g. here and here. Apparently, depending on the version of OpenSSL/LibreSSL, dates in the 'GeneralizedTime' format (YYYYMMDDHHMMSSZ) are not supported for years <2050. For these dates, the 'UTCTime' format should be used (YYMMDDHHMMSSZ). My ca.pem does appear to use the GeneralizedTime format (see here).

Suggested fix: use UTCTime format when date is before 2050, following RFC5280.

macOS (LibreSSL 2.8.3):

catest % openssl verify -CAfile ./ca.pem child.pem 
child.pem: CN = MyCA
error 13 at 1 depth lookup:format error in certificate's notBefore field
CN = MyCA
error 13 at 1 depth lookup:format error in certificate's notBefore field
CN = MyCA
error 13 at 1 depth lookup:format error in certificate's notBefore field

Linux (OpenSSL 1.1.1f 31 Mar 2020):

$ openssl verify -CAfile ./ca.pem child.pem 
child.pem: OK

Certificates

ca.pem

openssl x509 -in ./ca.pem -text -noout

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1 (0x1)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=MyCA
        Validity
            Not Before: Jun  4 20:15:46 2020 GMT
            Not After : May 30 20:15:46 2040 GMT
        Subject: CN=MyCA
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub: 
                    04:75:0c:8a:f4:f5:76:e7:fb:22:56:9c:dc:a7:6b:
                    9b:52:75:7a:d5:cd:e0:fd:52:3f:fc:99:58:dc:9a:
                    af:51:46:11:bc:66:8d:99:ac:71:59:ff:0c:9b:b6:
                    19:64:c3:80:ee:49:e0:87:38:35:4e:cd:5c:a5:52:
                    db:f9:fc:ba:15
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                93:5D:F8:17:62:41:59:76:96:2A:ED:42:D8:46:F4:4C:19:F0:30:BD
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: ecdsa-with-SHA256
         30:45:02:21:00:9d:c4:5f:18:0b:a4:32:1c:b1:9a:4f:1a:5d:
         8f:1d:98:94:a8:07:d8:1d:97:29:0f:0b:aa:09:1a:1a:e6:d6:
         87:02:20:31:9a:2c:73:3a:2a:02:67:60:58:98:8e:65:d4:67:
         77:f8:38:6f:cc:f1:5e:3e:e1:9b:21:6d:ad:c9:95:39:ee

child.pem

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 2 (0x2)
    Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN=MyCA
        Validity
            Not Before: Jun  4 20:15:46 2020 GMT
            Not After : Jun 14 20:15:46 2020 GMT
        Subject: CN=MyCA user
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub: 
                    04:11:3c:17:b6:62:0b:07:cd:b8:91:ee:7d:55:d9:
                    88:7d:d0:da:44:ef:3c:d6:84:5a:75:8f:79:a5:9e:
                    d3:a4:37:35:53:af:e3:1e:11:27:90:11:c2:89:34:
                    91:1e:b4:75:de:5d:02:87:91:30:76:fe:50:91:0d:
                    97:6b:d6:4d:5f
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication
    Signature Algorithm: ecdsa-with-SHA256
         30:46:02:21:00:97:4d:87:2e:0d:f4:9a:bc:5f:ca:ae:03:a8:
         ff:d4:52:22:ab:08:f6:48:ac:50:55:a5:5d:10:fb:f3:b7:d2:
         c3:02:21:00:f5:c6:48:30:6c:a8:4a:1a:a0:2b:91:59:3a:8e:
         14:8f:61:06:9a:79:23:05:6e:ab:26:dc:30:32:55:10:83:04

Code used to generate

use rcgen::{Certificate, CertificateParams, IsCa, BasicConstraints, DistinguishedName, DnType, ExtendedKeyUsagePurpose};
use std::result::Result;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
use std::time::SystemTime;
use chrono::{DateTime, Utc};

fn main() -> Result<(), Box<dyn Error>> {
    let ca_validity = std::time::Duration::from_secs(86400 * 365 * 20);
    let child_validity = std::time::Duration::from_secs(86400 * 10);
    let ca_expiry_date = DateTime::<Utc>::from(SystemTime::now().checked_add(ca_validity).unwrap());
    println!("expiry_date={:?}", ca_expiry_date);

    // Create CA
    let mut ca_dn = DistinguishedName::new();
    ca_dn.push(DnType::CommonName, "MyCA");
    let mut params = CertificateParams::new(vec![]);
    params.distinguished_name = ca_dn;
    params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
    params.not_before = DateTime::<Utc>::from(SystemTime::now());
    params.not_after = ca_expiry_date;
    params.serial_number = Some(1);
    let ca = Certificate::from_params(params).unwrap();
    let ca_pem = ca.serialize_pem().unwrap();
    let mut file = File::create("ca.pem")?;
    file.write_all(ca_pem.as_bytes())?;

    let ca_key = ca.serialize_private_key_pem();
    let mut key_file = File::create("ca.key.pem")?;
    key_file.write_all(ca_key.as_bytes())?;
    

    // Create signed cert
    let child_expiry_date = DateTime::<Utc>::from(SystemTime::now().checked_add(child_validity).unwrap());
    let mut child_dn = DistinguishedName::new();
    child_dn.push(DnType::CommonName, "MyCA user");
    let mut child_params = CertificateParams::new(vec![]);
    child_params.distinguished_name = child_dn;
    child_params.not_before = DateTime::<Utc>::from(SystemTime::now());
    child_params.not_after = child_expiry_date;
    child_params.is_ca = IsCa::SelfSignedOnly;
    child_params.serial_number = Some(2);
    child_params.extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth);
    child_params.extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth);

    let child_crt = Certificate::from_params(child_params).unwrap();
    let child_pem = child_crt.serialize_pem_with_signer(&ca).unwrap();
    let mut child_file = File::create("child.pem")?;
    child_file.write_all(child_pem.as_bytes())?;

    let child_key_pem = child_crt.serialize_private_key_pem();
    let mut child_key = File::create("child.key.pem")?;
    child_key.write_all(child_key_pem.as_bytes())?;

    Ok(())
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.