Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reqwest::Certificate::from_der and reqwest::Certificate::from_pem are inconsistent #1858

Open
carl-wallace opened this issue May 26, 2023 · 0 comments
Labels
C-bug Category: bug. Something is wrong. This is bad! E-pr-welcome The feature is welcome to be added, instruction should be found in the issue.

Comments

@carl-wallace
Copy link

The reqwest::Certificate::from_der and reqwest::Certificate::from_pem functions have some inconsistent behavior (at least on MacOS) that can result in some difficult to troubleshoot issues. When you pass a DER-encoded buffer to the from_pem function, it does not fail but it yields a Certificate object that does not work. It does not work because internally the parse function just saves the buffer in an enum that indicates it is PEM then later an attempt is made to parse the buffer as PEM and fails. Attempting to parse PEM using from_der fails (hence the inconsistency). Ideally, from_pem would fail when presented a DER buffer.

The sample code below used these dependencies:

tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] }
hex-literal = "0.4.1"
reqwest = {version = "0.11.18", features = ["rustls-tls"]}

The sample code include comments for generating test keys and certs using openssl and for using openssl s_server to stage the test. For parsing, each combination of PEM and DER is exercised, with only the attempt to parse DER using from_pem yielding unexpected results. One successfully parsed certificate and the non-functional certificate returned by from_pem are used to connect to the server (with the former working and the latter failing).

#[tokio::main]
async fn main() {
    // client.cnf
    // basicConstraints = CA:FALSE
    // subjectKeyIdentifier = hash
    // keyUsage = critical, digitalSignature, keyEncipherment
    // extendedKeyUsage = clientAuth
    // subjectAltName         = @sans
    //
    // [ sans ]
    // email.0 = ee@example.com

    // server.cnf
    // basicConstraints = CA:FALSE
    // subjectKeyIdentifier = hash
    // keyUsage = critical, digitalSignature, keyEncipherment
    // extendedKeyUsage = serverAuth
    // subjectAltName         = @sans
    //
    // [ sans ]
    // DNS.0 = localhost

    // openssl ecparam -out ca.key -name secp384r1 -genkey
    // openssl req -x509 -new -key ca.key -out ca.pem -outform pem -sha384
    // openssl ecparam -out ee.key -name secp384r1 -genkey
    // openssl req -new -nodes -key ee.key -outform pem -out ee.req -sha384
    // openssl x509 -req -in  ee.req -CA ca.pem -CAkey ca.key -CAcreateserial -out ee.crt -days 730 -sha384 -extfile client.cnf
    // openssl ecparam -out server.key -name secp384r1 -genkey
    // openssl req -new -nodes -key server.key -outform pem -out server.req -sha384
    // openssl x509 -req -in  server.req -CA ca.pem -CAkey ca.key -CAcreateserial -out server.crt -days 730 -sha384 -extfile server.cnf

    // run server
    // openssl s_server -cert server.crt -key server.key -WWW -port 12345 -CAfile ca.crt -verify_return_error -Verify 1

    use hex_literal::hex;
    let ee_pem = hex!("2D2D2D2D2D424547494E20454320504152414D45544552532D2D2D2D2D0A426755726751514149673D3D0A2D2D2D2D2D454E4420454320504152414D45544552532D2D2D2D2D0A2D2D2D2D2D424547494E2045432050524956415445204B45592D2D2D2D2D0A4D49476B41674542424441653935696C2F6D536651675871662F31553449537675374D6F6C652F6F6B5258532F662F795768762F5A355557742F52454234314E0A5776743364577749514B4367427759464B34454541434B685A414E694141536B654E5251773939533933346A76524D6A6E74456279566C46626E5958674A55390A6766655567794850554A765239316771477977702B796254754A6D2F49705656576135484C7475566B506231743569544C7A4E317A56736F4452322B704F34560A39715269682F6462616A3934625976474B566A692B534433754F317A3954513D0A2D2D2D2D2D454E442045432050524956415445204B45592D2D2D2D2D0A2D2D2D2D2D424547494E2043455254494649434154452D2D2D2D2D0A4D494942386A434341586967417749424167494A414E476542506D77334B7A6D4D416F4743437147534D343942414D444D436B78437A414A42674E56424159540A416C56544D513077437759445651514B444152555A584E304D517377435159445651514444414A4451544165467730794D7A41314D6A59784D5445334E5456610A467730794E5441314D6A55784D5445334E5456614D436B78437A414A42674E5642415954416C56544D513077437759445651514B444152555A584E304D5173770A435159445651514444414A46525442324D42414742797147534D343941674547425375424241416941324941424B52343146444433314C3366694F3945794F650A3052764A57555675646865416C54324239355344496339516D39483357436F624C436E374A744F346D6238696C56565A726B637532355751397657336D4A4D760A4D33584E5779674E4862366B3768583270474B48393174715033687469385970574F4C3549506534375850314E4B4E734D476F774351594456523054424149770A4144416442674E56485134454667515572593330613948366270316470646861374B67456E46737A7A73517744675944565230504151482F42415144416757670A4D424D47413155644A51514D4D416F47434373474151554642774D434D426B4741315564455151534D424342446D566C514756345957317762475575593239740A4D416F4743437147534D343942414D44413267414D4755434D51446C517676416E4F7166695A597378326633795058766472516B72424F707538796F336C6D6D0A6B5648556648313844666544476F5A5651313063582F4250647341434D41693947323147326A50644443654A49314177575164315662482B4C5A3843494F44670A74707A4753354D384C7A67515936457632376A6363775555737636356D413D3D0A2D2D2D2D2D454E442043455254494649434154452D2D2D2D2D0A");
    let pkcs8 = reqwest::Identity::from_pem(ee_pem.as_slice()).unwrap();

    let ca_der = hex!("3082018030820105020900B1151278481F6689300A06082A8648CE3D0403033029310B3009060355040613025553310D300B060355040A0C0454657374310B300906035504030C024341301E170D3233303532363131313133335A170D3233303632353131313133335A3029310B3009060355040613025553310D300B060355040A0C0454657374310B300906035504030C0243413076301006072A8648CE3D020106052B81040022036200042C131B0A3964CDAB2CDD5EBC2D4CB25D6D70C610B8136E62D539765EFF1A631FFCC9D11D4B8CCF6AB7E498D80E511B522C5FB6CC0BD3BEAA2FEE40AAB20A1C0DCED6DF4A2ECB938351CFB6B09A19307CC68DCD982344998B686E0C0C1C448320300A06082A8648CE3D0403030369003066023100AD4A03C7961D30A9B9F93B5E30151301D8DCEFA014970348A188147508EC05EC7BDC5106A08BCA1FD0EB759678BAED740231008397132BD62F6D86FCEE2D9AEF7B59B3B370880D55BD643FADE9A3E3A6D7F1607A6EAA45CABD1FFB1A783FF00307A835");
    let ca_pem = hex!("2D2D2D2D2D424547494E2043455254494649434154452D2D2D2D2D0A4D49494267444343415155434351437846524A345342396D6954414B42676771686B6A4F50515144417A41704D517377435159445651514745774A56557A454E0A4D41734741315545436777455647567A6444454C4D416B474131554541777743513045774868634E4D6A4D774E5449324D5445784D544D7A5768634E4D6A4D770A4E6A49314D5445784D544D7A576A41704D517377435159445651514745774A56557A454E4D41734741315545436777455647567A6444454C4D416B47413155450A4177774351304577646A415142676371686B6A4F50514942426755726751514149674E69414151734578734B4F57544E71797A6458727774544C4A64625844470A454C6754626D4C564F585A652F78706A482F7A4A3052314C6A4D3971742B535932413552473149735837624D43394F2B71692F75514B71794368774E7A7462660A5369374C6B344E527A3761776D686B77664D614E7A5A676A524A6D4C6147344D4442784567794177436759494B6F5A497A6A304541774D44615141775A6749780A414B314B413865574854437075666B37586A415645774859334F2B67464A6344534B47494648554937415873653978524271434C79682F5136335757654C72740A6441497841494F58457976574C3232472F4F34746D75393757624F7A6349674E5662316B503633706F2B4F6D312F4667656D367152637139482F736165442F770A4177656F4E513D3D0A2D2D2D2D2D454E442043455254494649434154452D2D2D2D2D0A");

    // attempting to parse PEM as DER fails as expected
    let ca_der_pem_res = reqwest::Certificate::from_der(ca_pem.as_slice());
    assert!(ca_der_pem_res.is_err());

    // attempting to parse DER as DER and PEM as DER work as expected
    let ca_der_der_res = reqwest::Certificate::from_der(ca_der.as_slice());
    assert!(ca_der_der_res.is_ok());
    let ca_pem_pem_res = reqwest::Certificate::from_pem(ca_pem.as_slice());
    assert!(ca_pem_pem_res.is_ok());

    // attempting to parse DER as PEM succeed but yields a Certificate object that does not work
    let ca_pem_der_res = reqwest::Certificate::from_pem(ca_der.as_slice());
    println!("ca_pem_der_res: {ca_pem_der_res:?}");
    let c = ca_pem_der_res.unwrap();

    let client = reqwest::Client::builder()
        .identity(pkcs8.clone())
        .connection_verbose(true)
        .use_rustls_tls()
        .add_root_certificate(ca_der_der_res.unwrap())
        .https_only(true)
        .build()
        .unwrap();

    let response = client.get("https://localhost:12345").send().await.unwrap();
    assert!(response.status().is_success());

    let client2 = reqwest::Client::builder()
        .identity(pkcs8)
        .connection_verbose(true)
        .use_rustls_tls()
        .add_root_certificate(c)
        .https_only(true)
        .build()
        .unwrap();

    // fails with invalid peer certificate: UnknownIssuer
    let response = client2.get("https://localhost:12345").send().await;
    println!("{response:?}");
}
@seanmonstar seanmonstar added C-bug Category: bug. Something is wrong. This is bad! E-pr-welcome The feature is welcome to be added, instruction should be found in the issue. labels May 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: bug. Something is wrong. This is bad! E-pr-welcome The feature is welcome to be added, instruction should be found in the issue.
Projects
None yet
Development

No branches or pull requests

2 participants