Problem with CSR for only one DNS name


#1

Hi,

I’m developing a acme client in PHP. My problem is that all work fine until I try to issue a certificate for only one DNS name. In other word if the subjectAltName contain almost two DNS names it’s ok and I can get a working certificate. But if the subjectAltName contain only one DNS name, I get the error :
{
“type”: “urn:acme:error:malformed”,
“detail”: “Error unmarshaling certificate request”,
“status”: 400
}

Here is my PEM encoded CSR :
-----BEGIN CERTIFICATE REQUEST-----
MIIC7DCCAdQCAQAwYTEZMBcGA1UEAwwQbnMyNi5pbmdlbmllLm5ldDELMAkGA1UE
BgwCRlIxDzANBgNVBAgMBkZyYW5jZTETMBEGA1UEBwwKTGUgQ2hleWxhczERMA8G
A1UECgwISW5nw6luaWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw
DsXprAwBdedAkaPBsMapzNRjVJHb6gNMFUX+/IklWOff5nSWJiWm70zDGYxqp91R
cT1v4PyWY4rBdzRmzPNV90if9FgN+rHIFVnkdS0KMK9pbZ7V2UB7VXVhr7EUTv3k
ShQaRJCZglnTfaD+kjcqYd4bynS0FDGrhsJdZqDhgXoBHUWEnDgPxpiZH00nWNMG
JFiSICN/INKaU4WPzSxIsPAH3gMQ2JQudHxMElr8VVCiQ+y0IHYTCFzEEQS1+2CC
xYHNWF1tX0JrZEUKN+GEEaowyG77uIIITOwrbDqPnRswWNuzoeJt4hVmagDx3g0S
sj1L7L5l/xkeO1+XX6W5AgMBAAGgRjBEBgkqhkiG9w0BCQ4xNzA1MAkGA1UdEwQC
MAAwCwYDVR0PBAQDAgXgMBsGA1UdEQQUMBKCEG5zMjYuaW5nZW5pZS5uZXQwDQYJ
KoZIhvcNAQEFBQADggEBAK0IjQ/Mgux2rXzDn1sCIjHM4UhrH3zpg4uk03dpD/5E
9eusfAJdubV/8VNLNOIJga9PUoZcBIoMK6L2Iuvwny3z51WIiLmbZglo9bcyFRYd
39WvxUwsObT2LUMKDB0VQ/O3mJ/Duw9DcEWf2LyGXhF88G6Q9uJCu2+bq7IxvJUK
o9guSML3f86F6KcP2Uu2tOFN3dPprcyE8ABoHKc5QrGdHmDqVEyQJPw3ydk6u+rA
uv5Kyu/8NZPxXiboSdm50GW3AyoXx/fhYxGZDn2/LKjnB9clkzC99aLXWUUB544t
OFsH/XSJddEeG8xz+qOAdBrH9AP7tAG8S3WAYI3OCnk=
-----END CERTIFICATE REQUEST-----

(I tried to add email address to the subjectAltName, it’s worst…)

Some help would be greatly appreciated.


#2

Hmm.

That CSR has a bunch of extraneous stuff in the Subject that will get deleted/ ignored by Let’s Encrypt. Let’s Encrypt does not verify your organisation’s name or place of business so it is not permitted to include claims about that in certificates it issues.

I am not sure, but I suspect that one of those extraneous fields also happens to either be mis-encoded or use an encoding that Let’s Encrypt doesn’t both implementing, which would explain the unmarshaling error.

Either way, I recommend generating the CSR with only DNS names in it, not countries, organisation names, hat sizes, favourite Sam Jackson movies, etc. and indeed, not email addresses. Please reply back if you have a CSR with only DNS names in it that triggers this error (including the CSR).


#3

Thanks a lot !

That was it. I removed all the distinguished name values but CN and it works like a charm.

Many thanks for the fast and judicious answer.


#4

Hi

In fact I haven’t test CSR with multiple DNS name when keeping only CN in DN. But it doesn’t work… I get the usual : {
“type”: “urn:acme:error:malformed”,
“detail”: “Error unmarshaling certificate request”,
“status”: 400
}

Here’s my PEM encoded CSR :
-----BEGIN CERTIFICATE REQUEST-----
MIICzzCCAbcCAQAwJDEiMCAGA1UEAwwZcmVzZXJ2YXRpb24uYWxwZWRodWV6LmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANzyrn5ll16pqbE13yCt
EUhZNlFDKm9A0Dx0vJ7z5dmcraedefNd0286/WnLPHNp9Kzx19Fs/xMNRoXKSxla
eJKu1WfMldGEzJd0fcWe4ymangv8u0Ti0suCZliT56D6AvUxtyI/cd06rjJ6ZL0x
2tpOpekJc1Msrf62SsEhA149eogQJKGPhvKy8fsOTEFTZJ71hE4uItlhAEy9mf6Y
Qhhw9x9P/3dyGeCb779tBsqi98SuSPJc11pAr4yVOn8bDL8J1j4sg5ROZ4QOhmXE
C2JP8UiEpmiTxgrQx+v9n5v3T6B7HwN+zWrIYfH07ki+YQt73oHeyvDqSTYLRYQB
FPsCAwEAAaBmMGQGCSqGSIb3DQEJDjFXMFUwCQYDVR0TBAIwADALBgNVHQ8EBAMC
BeAwOwYDVR0RBDQwMoIZcmVzZXJ2YXRpb24uYWxwZWRodWV6LmNvbYIVYm9va2lu
Zy5hbHBlZGh1ZXouY29tMA0GCSqGSIb3DQEBBQUAA4IBAQBDnSbLgZ9k+VeeHbk1
EW5ChjZE/6CLpqDRzIFusvWmxKKsCHg3WXwZtd/FZrdsZUnxyf+NXBZYZD2wjbWf
DnyKxEPyVwBy+YDbNWdmJEtxeeVEiyESAoAZ7muChN5yfBh7X0n0jR6Yg7BIplQX
zeWbs7hBnABK+PylUNJZAaGHyNA2c3g5rfOy858bGtCZRhF/LJTwgz5NkGAjZrxu
52ekLh/5Ei50JSK37zwbBTEHp+bpNvfu5r5hfYsEPM44ObZ6ri5Xh7tOniCDYYkR
aMBB4iEoadPg0syXJEgudigx91Y1kZZtk2kwNgogTnxCbWwDWigqFHsY7Yp+0Rb+
n7Bl
-----END CERTIFICATE REQUEST-----

I’m really curious to know what is the problem if someone can help.


#5

I noticed that your CSR is signed using SHA-1. I’m not quite sure if Let’s Encrypt is enforcing the signing algorithm for CSRs, but I’d recommend trying SHA-256 instead (that’s -sha256 with the openssl command line).

(Certificates certainly are always signed using SHA-256, but I’m not certain whether CSRs using SHA-1 would be acceptable.)


#6

Thanks for your answer.

using SHA-256 don’t change the problem. Same error.

Here is the new CSR :
-----BEGIN CERTIFICATE REQUEST-----
MIICzzCCAbcCAQAwJDEiMCAGA1UEAwwZcmVzZXJ2YXRpb24uYWxwZWRodWV6LmNv
bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfzUq6PZ8lReaV9tnos
ZtiozLH184Dov8F8BchMKZqDqfqYfcG0cfSu859CyzLClMG8QTB0i/0kDdeYSQBi
sRbmxRPIDWo7j3YReTzGgPojta7zzrqIAAMx2Zrv1MG4ySZN5el7/LtJ4mV4lFLe
enllCkaJhLEPuN2ROotsIkAWjuTbwy2yQ7K9btj6ePKnXjWH5gfsrfJk2fV3q5oK
BIlzy7c/gIosdJLeB7kqZz+8pufpCK9baGRQ+1j/E9ovbc8GE0RtlBo8eriw5ytQ
XkW5MPyWLt1tB+HrRWE0tBL0PrnAq7DWqJTva9rEKgmEAUEMUrOxkGsr8oRcS/kV
meMCAwEAAaBmMGQGCSqGSIb3DQEJDjFXMFUwCQYDVR0TBAIwADALBgNVHQ8EBAMC
BeAwOwYDVR0RBDQwMoIZcmVzZXJ2YXRpb24uYWxwZWRodWV6LmNvbYIVYm9va2lu
Zy5hbHBlZGh1ZXouY29tMA0GCSqGSIb3DQEBCwUAA4IBAQA7dCWk+N/xm/U/xqel
A6rmf7m39cljs1vnIK01eLe3n4LCgndjT/L7lwi02DP4qidggSwxzOxTqFx63Mwd
FOqgnyO+gEh9Bhilgni+u4jg/xT7gYvQ8dLiEIklFAYYWVjNuExuIsyY97dN8CHV
AsbodpUL3g+b4CbbDMkmMcymRil2pFlD6UQOrCu6dlmRltk8/tPA32taRJtnjjID
iQJ0jTwDGAJ37gy9Itpf9qsnyD1nJ1PO8M1MB7pEocfK160ihncyc3SG3lop2zKH
9oq0lKrZENxoc6ac/GDjgSkULXrjb0Xlzf42Hww6m/GgwmQWaTPkPkRrVUS/u9XF
wVX6
-----END CERTIFICATE REQUEST-----


#7

If I add C,O,L and ST to the DN it works with two DNS names or more.
With one DNS name it works only if DN contain only CN…

This CSR worked :
-----BEGIN CERTIFICATE REQUEST-----
MIIDEjCCAfoCAQAwZzEiMCAGA1UEAwwZcmVzZXJ2YXRpb24uYWxwZWRodWV6LmNv
bTELMAkGA1UEBgwCRlIxEDAOBgNVBAoMB0luZ2VuaWUxEDAOBgNVBAcMB0luZ2Vu
aWUxEDAOBgNVBAgMB0luZ2VuaWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCscfFPdtLO+6P6K2Pqa68z+05y0xXzxVt/ZlLXq2+JdDSsxJHjXxxdoetg
BLW0vONl6BXo+UZN3bt1Qrnaz/ssxObNqgIYC7cgRcxddsu0YXEu4qrSSA1teqVj
+wptN1mzNatRzk8BYflu/ZDG40XxEiesWIOYFsCDd8IR/AdPQ0dsNJooawOKu690
KaYeb/IkuybAnXkrVZQTLjIp17aJsvS8iNBuRQnz/fwjU4YfnaCMujcin4fj3qPv
fgwo5EALeCOAJwNW/eNT2mBpCx1tPsg4ocfwtQOXvH1gUi9MLYGXjAoXpB/RSm86
gG68mhb+yJVYGCv4HmsEp6+++jr5AgMBAAGgZjBkBgkqhkiG9w0BCQ4xVzBVMAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgXgMDsGA1UdEQQ0MDKCGXJlc2VydmF0aW9uLmFs
cGVkaHVlei5jb22CFWJvb2tpbmcuYWxwZWRodWV6LmNvbTANBgkqhkiG9w0BAQsF
AAOCAQEATQ85VQVsE+G2d6d26rZBP6xg1OxeCcvCM57svpZdnZhvnVrln1bJFTe0
EkgQ7diYfBAQMfow0NYvlM9D4bETYG6OowDORXzI4CxyRMMJ0dgZF03foN5ASPDX
i3/jRdqatbWZSCUD3DWo+6S7n5B05AJV1f5ES+kWOWZ1FW11iDHyL5g3RrC0dWWy
pB/ZM+JxOb5+OVkGA5kYERXj8F7DIsLBVcuLcRivoYu9ahTiWBxZccH/uWopSbgk
VUTsbnvJTFXLma+/17O3I9YLW2AejG5ED99S93JutwW75Q7+FdiKsPtg2Vin5TtW
nlhX1OUQM6ScsFYQecB0udU0lx42fw==
-----END CERTIFICATE REQUEST-----


#8

Hello Ingenie,

If you are using PHP to generate the CSR and interact with the CA to get it signed you should be able to take the same logic I am using. Check out https://github.com/metaclassing/PHP7-CertBot and specifically app/Acme/Account.php and app/Acme/Certificate.php for the CSR generation and ACME client logic to get it signed.


#9

Thanks for your answer.

It seems that the problem is corrected. So for now I’m pleased with my own code which work fine.


#10

Hi,

I can no more issue certificate for multiple domain names. (The used code use to work and still works for one name.)

The error is (the same as usual) :
{
“type”: “urn:acme:error:malformed”,
“detail”: “Error unmarshaling certificate request”,
“status”: 400
}

Here is my REQ :
-----BEGIN CERTIFICATE REQUEST-----
MIIC+DCCAeACAQAwKDEmMCQGA1UEAwwdcmVzZXJ2YXRpb24udmFsbGVlLW11bnN0
ZXIuZXUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE8KTYw1qXzrpq
EgYmsGrrgZIYYzQ/R4QwCnh3U+iBCD7v1Pl5VMI8+9HpjBfHK9ZXKCf7HJvPgv2V
6iGlU3sXnucG5YSkS/GlVvjyoD+onMvcHRnpKb7hwwEY/ynbSRT0HooEgjUOetsb
6KHzLrsgPTyjHaw9myRHCt680SxQpE2b3ONE60f3GQzLsMZU1c7xErCVDOBJsYMh
9eObd6AO5m7TPiS7MYFv4zMdDPlxkhl4jah8ajUrnmrHRMabgJtVPP1ZBe5kvq4h
eqXwfAjFz1/kg0TMXlDoPpfInCjTDIczLCQGPmOkGqzeTvzgkaBB1wPyIgSOlFZP
zimoib99AgMBAAGggYowgYcGCSqGSIb3DQEJDjF6MHgwCQYDVR0TBAIwADALBgNV
HQ8EBAMCBeAwXgYDVR0RBFcwVYIdcmVzZXJ2YXRpb24udmFsbGVlLW11bnN0ZXIu
ZXWCGWJvb2tpbmcudmFsbGVlLW11bnN0ZXIuZXWCGWJ1Y2h1bmcudmFsbGVlLW11
bnN0ZXIuZXUwDQYJKoZIhvcNAQELBQADggEBAFTc1nWsNdpa56AHynjXTqktILnI
Qy1c5aHaKB/BIvJaHkzkT4IUZPcBfq2zWGFBRL1zS17TIdXpI2VgOS5tdCmgkTVy
F6KmcAceI2plkZbs06FJ13rjl7e/gPDyszslyW6xnf7arKqyFev9Z7QVaYLx1OOj
qa+B/7EgxXLZlolEajwbjxG5ygulSauTVcoaJ046voIOEH2FD0ZDbH/Jah6fBvbn
cFA++dJMtF8Pf7C+IFrroWNK8pxBWhKA8CJRyYoZ8fFxXtCUJCuFGlXdmUmWyU4Q
zZjA8rG2O+tGv4SyTU4tjMFiSZmpF5Qkx+IkcrCCZg5fKaHhqS/bYaDqN7Q=
-----END CERTIFICATE REQUEST-----

Is it possible to have some information on what important rule this req is breaking ?

More important : is this service really usable or must I give up ? I must take a decision.

Many thanks for considering my request.


#11

The “Requested Extensions” X509v3 Basic Constraints and X509v3 Key Usage are not used. (Actually, only the CN and SAN fields are used.) So you might want to try to minimise the extras in the CSR.

Also, try to explicitely set the Version field to something else than “0” (default for OpenSSL <1.0.2). Version fields of zero generate an error on staging server (and live from January 2017). (See “Rejection of malformed CSRs”).

I’m sure one of those (probably the latter) will fix the problem.


#12

Hi,

Thanks for your answer.

I tried to create CSR with openssl (My version of openssl is “OpenSSL 1.0.2g-fips 1 Mar 2016”) and still I have a version field of 0. I don’t see any way to specify de version field. Can you give me an example of a tool which create a “LE well formed” CSR ?

And why can I issue certificate when the SAN contain only one name even with a version field of 0 ?


#13

Helo metaclassing

I read your code. In fact we use exactly the same library phpseclib to generate the CSR. So if your code is still working I’m interested in what you do and I don’t. (I could download your entire code and try it myself but I’m a lazy guy. So if you’re still around here I’d appreciate if you can give me some news about it.)


#14

Hi Ingenie,

When you say “still working” help me understand. Has your code worked and then stopped working after an upgrade?

What version of phpseclib specifically are you on? I was on 2.0.2 and it worked, but found a bug in 2.0.3 but was a quick 1 line fix already in the master branch.

Do you have a sample CSR you generate you could post? I found using an ASN.1 decoder to compare working/not working CSRs and certs to be most helpful.

My CSR generation code for acme certs specifically is this: https://github.com/metaclassing/PHP7-CertBot/blob/master/app/Acme/Certificate.php#L117-L157

phpseclib has quirks when setting X509 v3 extended attributes, I have to sign the CSR with the certs private key, and then reload it to append cA=>false, keyUsage, and subject alternative names, then sign it again to generate the final working CSR to submit to the ACME authority. If you set cA=>false and mark the attribute CRITICAL, boulder will reject it even though this IS a valid and good setting.

Terrafrost is an excellent resource for crypto questions and while traveling at the moment is usually pretty responsive if you open an issue on github.com/phpseclib/phpseclib - I dont know how much he knows about ACME authorities and their wants/needs/desires but could correct any (mis)use of the library.

Please let me know, attach any example csr or certs you have so i can decode them to compare output, and if you have code in github I can read that would be easiest to help troubleshoot.


#15

Hi metaclassing,

Thanks for your answer.

“still working” mean : have you tried your code lately ? My code didn’t change, was working and stop working after boulder upgrade.

I use the phpseclib 2.0.2 and do exactly the same thing as you to add a SAN extension (sign, append extensions then sign again). I gave a PEM encoded example of the resulting CSR above.

I also tried to create a CSR with openssl 1.0.2… no more success.

The same code work with extension containing only one name in SAN. The CSR is accepted and the Go library do not have trouble to “unmarshall” it.

For now I don’t have many time to spend on it. I’ll make new try later.


#16

Hi Ingenie,

I created a ‘similar’ certificate in my tool, generated a keypair, generated a csr, and got it signed. Here is an online comparison of our two certificate signing requests:
https://lapo.it/asn1js/#308202F8308201E002010030283126302406035504030C1D7265736572766174696F6E2E76616C6C65652D6D756E737465722E657530820122300D06092A864886F70D01010105000382010F003082010A0282010100C4F0A4D8C35A97CEBA6A120626B06AEB81921863343F4784300A787753E881083EEFD4F97954C23CFBD1E98C17C72BD6572827FB1C9BCF82FD95EA21A5537B179EE706E584A44BF1A556F8F2A03FA89CCBDC1D19E929BEE1C30118FF29DB4914F41E8A0482350E7ADB1BE8A1F32EBB203D3CA31DAC3D9B24470ADEBCD12C50A44D9BDCE344EB47F7190CCBB0C654D5CEF112B0950CE049B18321F5E39B77A00EE66ED33E24BB31816FE3331D0CF9719219788DA87C6A352B9E6AC744C69B809B553CFD5905EE64BEAE217AA5F07C08C5CF5FE48344CC5E50E83E97C89C28D30C87332C24063E63A41AACDE4EFCE091A041D703F222048E94564FCE29A889BF7D0203010001A0818A30818706092A864886F70D01090E317A307830090603551D1304023000300B0603551D0F0404030205E0305E0603551D1104573055821D7265736572766174696F6E2E76616C6C65652D6D756E737465722E65758219626F6F6B696E672E76616C6C65652D6D756E737465722E6575821962756368756E672E76616C6C65652D6D756E737465722E6575300D06092A864886F70D01010B0500038201010054DCD675AC35DA5AE7A007CA78D74EA92D20B9C8432D5CE5A1DA281FC122F25A1E4CE44F821464F7017EADB358614144BD734B5ED321D5E9236560392E6D7429A091357217A2A670071E236A659196ECD3A149D77AE397B7BF80F0F2B33B25C96EB19DFEDAACAAB215EBFD67B4156982F1D4E3A3A9AF81FFB120C572D99689446A3C1B8F11B9CA0BA549AB9355CA1A274E3ABE820E107D850F46436C7FC96A1E9F06F6E770503EF9D24CB45F0F7FB0BE205AEBA1634AF29C415A1280F02251C98A19F1F1715ED094242B851A55DD994996C94E10CD98C0F2B1B63BEB46BF84B24D4E2D8CC1624999A9179424C7E22472B082660E5F29A1E1A92FDB61A0EA37B4
and
https://lapo.it/asn1js/#308202F8308201E002010030283126302406035504030C1D7265736572766174696F6E2E7365637572656F6273637572652E636F6D30820122300D06092A864886F70D01010105000382010F003082010A0282010100B3B0B456E06CE981D4CD74050BB9B3EAF780FB6CD6C4BAD377C9D7E5D853C16DC11132E7EEE00D87DD6B4C4F390CF8BFAE0EB7257E4DBED645FC41086E7359CE1911088A59E07FEB07BFA11F969A74EEE9E4DD22EE521C8CEE5D9EB83D83524EF283D4AC7B4C07411BAEC922A6EC34DBB10BE3546943148661EF6AAE9FFB691194F8F4AF4F42DB12A230831413F3E249D9CEFD7235A80067800849BAB02A8BD222EAD3CC977F4E1F89E1ECB7BBDD9C56C1586B5C4F864FC3BAB43F0C92E3636AB3C40CF37C4C885AFB56F236A321079D9EF9C484B76EA85C11BF070B6720C9251654270C5200893FE0D83C6D9FCA5A7486668F1A676F89DBCBF1856B205C18C30203010001A0818A30818706092A864886F70D01090E317A307830090603551D1304023000300B0603551D0F0404030205E0305E0603551D1104573055821D7265736572766174696F6E2E7365637572656F6273637572652E636F6D8219626F6F6B696E672E7365637572656F6273637572652E636F6D821962756368756E672E7365637572656F6273637572652E636F6D300D06092A864886F70D01010B050003820101000F9467586B7F9C97714F4CC89316596D80CABCE0FE533BA0DDB8B1373B03A10538E23FA89AC92C366C7B73E7FC2C3C0545C8552B1A2087837A32B2D3D789E7CDFAE51CC231C293E1D25330CE1426D8BE7D3594594E9048D7AA19FA6210F58AACD334929C09424E67363E87F5514E2288C745B8D5E88A6CC01EA3B9CEF021C58CDEF7EED78BAD71ED845FC8F6C366187817C21221367EC58C9E7834ED752E24755480B1118D4F0E01EF922151E47DD40BC2BC3F74E3AA975DB1774AE385D59988CBC99CC5E94B1D5D50199A8C448B485F1C2D7FD07B5D68DC7EE01D4BA79C6A3319CC0AE1F7D4D38A6658A3A51ACADD534355F23132701BEE840EEE90389453D2
You should be able to flip between the two CSR’s they are structurally identical in their ASN.1 encoding, just different domain names and keys.
I have never generated a cert with LE for .EU or hyphenated domain names but dont believe that would be an issue. The only thing I can think of is that something somewhere is getting mangled in the JSON sent to Boulder. I generate a log file of all transactions with LE including JSON dumps for debugging purposes, I am curious what you are sending to get the error unmarshaling certificate request:

(
    [method] => POST
    [url] => /acme/new-cert
    [headers] => Array
        (
            [0] => Accept: application/json
            [1] => Content-Type: application/json
        )
[data] => 
{
   "protected" : "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6IlJTQSIsIm4iOiJ5ZDh2akpzUmRzSE81WHVpZi1BWFBfWHliRXZGZDgyRWdWbkIwWDNPTGI4bWJqbXlsSzR0N1Q0SUZfaEFEQ0dtRUJUcWM0eDh1VXlxYlJKT3ROLXlpZG45UG5idUVDVnJEZnY0djV0T05qNGxTRi11em40eF9aZVZ1cDNyV1V0S0QwTE5CZnVyREdKNlREOVVJZ2ZDWDFDVGw3b0tYc1lzNnZody1xMnJUeXkzbFoxRGRJQTREU0daY1ZfR2xYdVJOQlpVYm1sME1GOUhnNWlJd0xNRUhSckhaaG9LQ0NTMW5GYWloUlZGS05tWVI5X1JMZFpzekRhUHhMM3RhbXB2NmgxTGNCRzlPNnpBV3FLTmtIMkV5LU1kMlNudzV2c2hKZjN5cmpGR0NJR1JXbFd0aUhkLWU3ZElSUXdQQkxUUFE0Ui1ZdTk3RldqZDl6b3JKbHhhS0JWdjZzaU9uc2VzWEtfSWwzQ29uZ2I4ajAyR3pudkxvYS16N2JZYmFzMk01MWJ5VFJyRWRUZ1hqS2t3OEVvT2JHUE9ycDVnZG1vLTM2S2RaWjRGWFRZazBMZGloS2Nxcm1vZHpKckl4UE5zMXpKMm5tbDFwV1M0b2RWRWNJUVo3aUtuc2RpRmJqV1Y3Q2plNWlsb1hhVi0xSmcxV0ZHVjVpcXlzMkpfMmdMRVlreDVMRlNBRjNJUWFyVW9NcGNKYXdZRXc3N1ZQTEFNTlpWaml1cng5MVM4elEyU2VKdkFzTnBLaU83ejBRN0gzZ3QxclRsdlNxWktDVkVyRUZuYXFZODdmalVIcFQ4cDZ1UlBxZzRZYXBCeUpWdnlRMU9NYVM2MF8zUFllWHBud2p4WlZIbVppQ2hWdENDeHpWd0Jha2s4RHJKR214UGJXd3BZeEFxa0oyVSJ9LCJub25jZSI6ImRrTEthWVFwaUtWZWJIc3FaTUZHUklVTHdWV3pLeXdNYmg5bzc5b1JHRVEifQ",
   "header" : {
      "jwk" : {
         "kty" : "RSA",
         "e" : "AQAB",
         "n" : "yd8vjJsRdsHO5Xuif-AXP_XybEvFd82EgVnB0X3OLb8mbjmylK4t7T4IF_hADCGmEBTqc4x8uUyqbRJOtN-yidn9PnbuECVrDfv4v5tONj4lSF-uzn4x_ZeVup3rWUtKD0LNBfurDGJ6TD9UIgfCX1CTl7oKXsYs6vhw-q2rTyy3lZ1DdIA4DSGZcV_GlXuRNBZUbml0MF9Hg5iIwLMEHRrHZhoKCCS1nFaihRVFKNmYR9_RLdZszDaPxL3tampv6h1LcBG9O6zAWqKNkH2Ey-Md2Snw5vshJf3yrjFGCIGRWlWtiHd-e7dIRQwPBLTPQ4R-Yu97FWjd9zorJlxaKBVv6siOnsesXK_Il3Congb8j02GznvLoa-z7bYbas2M51byTRrEdTgXjKkw8EoObGPOrp5gdmo-36KdZZ4FXTYk0LdihKcqrmodzJrIxPNs1zJ2nml1pWS4odVEcIQZ7iKnsdiFbjWV7Cje5iloXaV-1Jg1WFGV5iqys2J_2gLEYkx5LFSAF3IQarUoMpcJawYEw77VPLAMNZVjiurx91S8zQ2SeJvAsNpKiO7z0Q7H3gt1rTlvSqZKCVErEFnaqY87fjUHpT8p6uRPqg4YapByJVvyQ1OMaS60_3PYeXpnwjxZVHmZiChVtCCxzVwBakk8DrJGmxPbWwpYxAqkJ2U"
      },
      "alg" : "RS256"
   },
   "signature" : "jrS04PAReqT2gj1ZF3ODufD4sNSzEI4_307Y0xb7VFpWcK0RJHi4Uh34nZDHoNEXKcjr-9CTomqUGR0XcR8DIcK8iHIeW4g-0uoiFuG52ksGZoo1ODZYO8PxiYIoRvb8g5tmZkDZprylK2kNFHBB5ca2lrP39ceGW85iAtpGeRD0gYjXebLmdMz9IflWP-ONBh-nJD6GF0IrMC7WGjEyQ1L72aAUOi69FF299_tCb_ljy2A9z84RV4lDW44RYNKwqim4rM9vchBTy4t1jCuV7-YhE4oupW-IWkFhnmGf04o7eL1U5-6ItQajInphSUXjTcm3XLq-jlSonnywXI3OaslNJRIv5w3yM7SIis0y9iOvigcTdQfWZCOKuDUvGs1Fyo_GgiOoWYkthV9yZMpgMO-ogQI5tBUMJ-i4COZUlUODHo1T33S9jnK9l1_VxKUFuVKYbyUJsp6iyVzcSfi9n-zEVEK2K60y0KYf5KjujVCZcGE0hQ2pm-_0iGs0Q7sLm_0-qp82h6b4UstEioqqFG4FnAfeSI22a2-Eyq-yMbDGglgoLy3BU3MtqsNo9u-fAJwMCHxxs9zmTrwJVk9KHLseyACt037fhlPZmhJghmqfhrD_cbXkaTVLCmKx4HXGVVaNX3AqlzTCigrss_brekW6zl8NXFFdOyrycnvQmso",
   "payload" : "eyJyZXNvdXJjZSI6Im5ldy1jZXJ0IiwiY3NyIjoiTUlJQy1EQ0NBZUFDQVFBd0tERW1NQ1FHQTFVRUF3d2RjbVZ6WlhKMllYUnBiMjR1YzJWamRYSmxiMkp6WTNWeVpTNWpiMjB3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ3pzTFJXNEd6cGdkVE5kQVVMdWJQcTk0RDdiTmJFdXROM3lkZmwyRlBCYmNFUk11ZnU0QTJIM1d0TVR6a00tTC11RHJjbGZrMi0xa1g4UVFodWMxbk9HUkVJaWxuZ2Ytc0h2NkVmbHBwMDd1bmszU0x1VWh5TTdsMmV1RDJEVWs3eWc5U3NlMHdIUVJ1dXlTS203RFRic1F2alZHbERGSVpoNzJxdW5fdHBFWlQ0OUs5UFF0c1NvakNERkJQejRrblp6djF5TmFnQVo0QUlTYnF3S292U0l1clR6SmRfVGgtSjRleTN1OTJjVnNGWWExeFBoa19EdXJRX0RKTGpZMnF6eEF6emZFeUlXdnRXOGphaklRZWRudm5FaExkdXFGd1J2d2NMWnlESkpSWlVKd3hTQUlrXzROZzhiWl9LV25TR1pvOGFaMi1KMjh2eGhXc2dYQmpEQWdNQkFBR2dnWW93Z1ljR0NTcUdTSWIzRFFFSkRqRjZNSGd3Q1FZRFZSMFRCQUl3QURBTEJnTlZIUThFQkFNQ0JlQXdYZ1lEVlIwUkJGY3dWWUlkY21WelpYSjJZWFJwYjI0dWMyVmpkWEpsYjJKelkzVnlaUzVqYjIyQ0dXSnZiMnRwYm1jdWMyVmpkWEpsYjJKelkzVnlaUzVqYjIyQ0dXSjFZMmgxYm1jdWMyVmpkWEpsYjJKelkzVnlaUzVqYjIwd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBLVVaMWhyZjV5WGNVOU15Sk1XV1cyQXlyemdfbE03b04yNHNUYzdBNkVGT09JX3FKckpMRFpzZTNQbl9DdzhCVVhJVlNzYUlJZURlakt5MDllSjU4MzY1UnpDTWNLVDRkSlRNTTRVSnRpLWZUV1VXVTZRU05lcUdmcGlFUFdLck5NMGtwd0pRazVuTmo2SDlWRk9Jb2pIUmJqVjZJcHN3QjZqdWM3d0ljV00zdmZ1MTR1dGNlMkVYOGoydzJZWWVCZkNFaUUyZnNXTW5uZzA3WFV1SkhWVWdMRVJqVThPQWUtU0lWSGtmZFFMd3J3X2RPT3FsMTJ4ZDByamhkV1ppTXZKbk1YcFN4MWRVQm1hakVTTFNGOGNMWF9RZTExbzNIN2dIVXVubkdvekdjd0s0ZmZVMDRwbVdLT2xHc3JkVTBOVjhqRXljQnZ1aEE3dWtEaVVVOUkifQ"
}
    [response] => HTTP/1.1 100 Continue
Expires: Fri, 16 Sep 2016 09:57:44 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache

HTTP/1.1 201 Created
Server: nginx
Content-Type: application/pkix-cert
Content-Length: 1368
Boulder-Request-Id: akOGk3xfSTphyNMG6LtFLFO_OTtKHbq087l5UERXcDw
Boulder-Requester: 2267141
Link: <https://acme-v01.api.letsencrypt.org/acme/issuer-cert>;rel="up"
Location: https://acme-v01.api.letsencrypt.org/acme/cert/03c1ea1c9191d27d5d6527a83f35d3b5aa24
Replay-Nonce: jdI_zr8or2_9KIWlr1f4Lmg_oFIrdyiJRVg0IQTwlK0
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800
Expires: Fri, 16 Sep 2016 09:57:44 GMT
Cache-Control: max-age=0, no-cache, no-store
Pragma: no-cache
Date: Fri, 16 Sep 2016 09:57:44 GMT
Connection: keep-alive

0T0<}]e'?5$0*H0J10UUS10U
Let's Encrypt1#0!
...
)

#17

Hi metaclassing,

Tell me if I’m wrong : if there really is a problem with de way I create and submit JWS with the CSR as payload, any CSR would give the same error. But I can issue certificate for each name alone… So I don’t understand. I keep looking for differences between our two requests. Thanks for your help.

Here is my post request body :

{"header":{"alg":"RS256","jwk":{"kty":"RSA","n":"vUTInXcMeB4BNwFsIL6ZyTS3uU5HtQhwq9K3p2BBow4KumNXenavDageh40RuAp15sImDovKSC-dB4PtgCr8xg9389mQ06_MqvtF846JcocJnAbgdU3Fa503kgKkvO7gZEvjkXcQgkZuQ3NhQ2iyfSKeHEVXB5jcCPzVdoTqJrC3xcP3TXIhEIw5EN1JT9gaskm3xVY51s6nsQx-y51yOeFyytWLeefQMzlTLimYlF2xMtv5aoCA5xst5kfXXcwi13fAP2MdUtHQUoO8ogUT2R4c3VVOi6HPCxS4fNICDk3xR_8CUXCxMY7LUUW_Rx0Vf-J5Y32LH5DsxjbN24NmGQ","e":"AQAB"}},"protected":"eyJhbGciOiJSUzI1NiIsImp3ayI6eyJrdHkiOiJSU0EiLCJuIjoidlVUSW5YY01lQjRCTndGc0lMNlp5VFMzdVU1SHRRaHdxOUszcDJCQm93NEt1bU5YZW5hdkRhZ2VoNDBSdUFwMTVzSW1Eb3ZLU0MtZEI0UHRnQ3I4eGc5Mzg5bVEwNl9NcXZ0Rjg0Nkpjb2NKbkFiZ2RVM0ZhNTAza2dLa3ZPN2daRXZqa1hjUWdrWnVRM05oUTJpeWZTS2VIRVZYQjVqY0NQelZkb1RxSnJDM3hjUDNUWEloRUl3NUVOMUpUOWdhc2ttM3hWWTUxczZuc1F4LXk1MXlPZUZ5eXRXTGVlZlFNemxUTGltWWxGMnhNdHY1YW9DQTV4c3Q1a2ZYWGN3aTEzZkFQMk1kVXRIUVVvTzhvZ1VUMlI0YzNWVk9pNkhQQ3hTNGZOSUNEazN4Ul84Q1VYQ3hNWTdMVVVXX1J4MFZmLUo1WTMyTEg1RHN4amJOMjRObUdRIiwiZSI6IkFRQUIifSwibm9uY2UiOiJJNTR6Z2Q1TWlHUEt6Q1RUVXZpOTdRcFBRSzJoVDFwaHQ5VjJwZkdQckpzIn0","payload":"eyJyZXNvdXJjZSI6Im5ldy1jZXJ0IiwiY3NyIjoiTUlJQzlEQ0NBZDRDQVFBd0tERW1NQ1FHQTFVRUF3d2RjbVZ6WlhKMllYUnBiMjR1ZG1Gc2JHVmxMVzExYm5OMFxyXG5aWEl1WlhVd2dnRWdNQXNHQ1NxR1NJYjNEUUVCQVFPQ0FROEFNSUlCQ2dLQ0FRRUFyMnZmdHpwc2ZsM3J5dDQ0XHJcblphTGFUMDRuZ3VnU2Y3NkY0Z2lkTXNaZmk2MHlHSVJ6dmJuWm82djh0MlJBdG1ES3lsc2F4SXlrSWQ0QS12MXVcclxuSlFIbDBOUm9nY1RIaGFQZkhQbWRpZW1SeVR2cmRnOEdWWDJBS2h1cHJSWEhId3BqOER3Ui1GV0xpcEVmZVNySVxyXG5QVzFteTVvSU9MajhXdFU0V3NQUEpJZXlaTnZsVjBNZkItX1N4SnZScHJpNEoxWllOdTNCSkxKYXFEZ2RqcUV6XHJcblFsbXpHMkJ3TjBwYUxJTUVGNzlyTlc1Nnc1Tzl3dEFyQWFlSEIweVpmSGo5c3BKYjBrWE1JN1lJN2tHNGxJbjFcclxuS2lrbFU5MHVFdDdNUjlEaFhEVmhpU2pBSUNjSndORDJheV9MMmNSd3lneGViUE9nRWFnbkNPMGVRZ3daRWU4SFxyXG45RTNGeXdJREFRQUJvSUdLTUlHSEJna3Foa2lHOXcwQkNRNHhlakI0TUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQXHJcbkJBUURBZ1hnTUY0R0ExVWRFUVJYTUZXQ0hYSmxjMlZ5ZG1GMGFXOXVMblpoYkd4bFpTMXRkVzV6ZEdWeUxtVjFcclxuZ2hsaWIyOXJhVzVuTG5aaGJHeGxaUzF0ZFc1emRHVnlMbVYxZ2hsaWRXTm9kVzVuTG5aaGJHeGxaUzF0ZFc1elxyXG5kR1Z5TG1WMU1Bc0dDU3FHU0liM0RRRUJDd09DQVFFQVRrajNwTlYzY3g5blVzNDl4MUFuN0k2T1BPNHNMbDNBXHJcblBaZjYxS0V2S2gxcThFOEZQZThQdFE4UENDVkFzZER3RGQxRjhlMFp5SUJEODdYRnRBWW5mbzBjX3BBbTJ1emtcclxuTEQxZC1hNk41T3pHWmZSWFA5MzhUdTBwcUIwT2xZOWwzV0x5SzdJMEFObFotNzh3a09YU0tfRmNUakttQmFCbVxyXG5yakJzQTcxU3BnZGxRVHQ3RzVKMmpvOEQ0ejE0cTQzS1JvZ18wQU9lelFac3FqQ3FpMzl5YlJMSW9wWW9NU29PXHJcbkFFbFZ6OGY2VFlPN1pia3NBQnBVLUlRYmFIZ3AtVHMtdEFxOTNaaGNrNEIyLWxKYlY2ZXgzRDJyYVFLT1ZERk1cclxudjlUWUNUU2FCTllfVUMzX0d6c2ZuUEgwMEc1OWl5Ui00WklIOVMyZUd2MVc5VlFORTRqR0d3In0","signature":"lSTPEVkxGs8r36IQ2bvFPgU6vrPqONJzHPeqfg6aK0O0bPJEiulqrw_6zuF7yo1D-CvZya6eZ9hBc7VWr99C4v9a8yy83EU3N8BxAMlHcjpH4jVMgijvrGDR4_-UXtb3IYNNkxaYbCVczsymxfaRlgw5ZN8saLiqL9RK9opcaUPEWJyP3DZi9N9Z7L8ZjjcI28MvA8f3vToes0DM4L5tXi_90okNwLhhDQS4Owy1GONlKQuC1uu5uUw3y8bvC1d6MkRqOvdC-TSrfFBNo4wQXyEsNb2TzZRsusema8AZX_6f8gOfZuo-TzqE-GddocYfg_cGbgrrTZRCZHOIKHKuYA"}

Here is my acme class code :

<?php

namespace admin\certification;

class acme
{
    protected $key;        // openSSL private key ressource
    protected $url;        // acme service base URL
    protected $urls;    // array of services URLs
    protected $nonce;    // last returned replay nonce
    protected $code;    // last returned HTTP code
    protected $header;    // associative array of last returned HTTP header attributes
    protected $body;    // last returned HTTP body

    /**
     * @param resource $key an openSSL private key resource
     * @param string $url base acme service URL
     **/ 
    public function __construct($key,$url)
    {
        $this->key = $key;
        $this->url = rtrim($url,'/');
        $this->_get($this->url.'/directory');
        $this->urls = $this->body;
    }

    public static function ACMEBase64url($str)
    {
        return rtrim(strtr(base64_encode($str),'+/','-_'),'=');
    }

    public static function PEM($str)
    {
        return sprintf("-----BEGIN CERTIFICATE-----\n%s-----END CERTIFICATE-----\n",chunk_split(base64_encode($str),64,"\n"));
    }

    /**
     * return a JWS containing $payload data and signed with the private key
     * @param string $payload the payload of the JWS
     * @return string JWS
     **/
    protected function _sign($payload)
    {
        $details = openssl_pkey_get_details($this->key);
        $header = array(
            'alg' => 'RS256',
            'jwk' => array(
                'kty' => 'RSA',
                'n' => self::ACMEBase64url($details['rsa']['n']),
                'e' => self::ACMEBase64url($details['rsa']['e']),
            )
        );
        $protected = $header;
        $protected['nonce'] = $this->nonce;
        $payload64 = self::ACMEBase64url(str_replace('\\/', '/', json_encode($payload)));
        $protected64 = self::ACMEBase64url(json_encode($protected));
        openssl_sign($protected64.'.'.$payload64,$signed,$this->key,OPENSSL_ALGO_SHA256);
        $signed64 = self::ACMEBase64url($signed);
        $data = array(
            'header' => $header,
            'protected' => $protected64,
            'payload' => $payload64,
            'signature' => $signed64
        );
        return json_encode($data);
    }

    /**
     * fill the header property with indexed array
     * @param string $header row HTTP header
     **/
    protected function _parse_header($header)
    {
        $this->header = array();
        $lines = explode("\r\n",trim($header));
        foreach ($lines as $line)
        {
            if (preg_match('|^([a-z-]+)\h*:\h*(.*)$|i',$line,$matches))
            {
                $key = strtolower($matches[1]);
                if (array_key_exists($key,$this->header))
                {
                    if (!is_array($this->header[$key]))
                        $this->header[$key] = array($this->header[$key]);
                    $this->header[$key][] = $matches[2];
                }
                else
                    $this->header[$key] = $matches[2];
            }
            elseif ($line === '')
                $this->header = array();
        }
    }

    protected function _url($service)
    {
        if (array_key_exists($service,$this->urls))
            return $this->urls[$service];
        else
            return $this->url.$service;
    }

    /**
     * Issue a HTTP GET request and fill the code, nonce, header and body properties
     * @param string $service relative URL or index of urls property
     **/
    protected function _get($url,$headers = array('Content-Type: application/json','Accept: application/json'))
    {
        $handle = curl_init($url);
        curl_setopt($handle,CURLOPT_HTTPHEADER,$headers);
        curl_setopt($handle,CURLOPT_RETURNTRANSFER,true);
        curl_setopt($handle,CURLOPT_HEADER,true);
        $response = curl_exec($handle);
        if(curl_errno($handle))
            throw new Exception('Curl: '.curl_error($handle));
        $this->code = curl_getinfo($handle, CURLINFO_HTTP_CODE);
        $header_size = curl_getinfo($handle,CURLINFO_HEADER_SIZE);
        $this->_parse_header(substr($response,0,$header_size));
        $this->nonce = (array_key_exists('replay-nonce',$this->header))?$this->header['replay-nonce']:null;
        print_r($this->header);
        if ($this->header['content-type'] === 'application/json')
        {
            $this->body = json_decode(substr($response,$header_size),true);
            print_r($this->body);
        }
        else
            $this->body = substr($response,$header_size);
        curl_close($handle);
    }

    protected function _post($url,$data,$headers = array('Accept: application/json'))
    {
        $headers[] = 'Content-Type: application/json';
        $handle = curl_init($url);
        curl_setopt($handle,CURLOPT_HTTPHEADER,$headers);
        curl_setopt($handle,CURLOPT_RETURNTRANSFER,true);
        curl_setopt($handle,CURLOPT_HEADER,true);
        curl_setopt($handle,CURLOPT_POST,true);
        curl_setopt($handle,CURLOPT_POSTFIELDS,$data);
        $response = curl_exec($handle);
        if(curl_errno($handle))
            throw new Exception('Curl: '.curl_error($handle));
        $this->code = curl_getinfo($handle, CURLINFO_HTTP_CODE);
        $header_size = curl_getinfo($handle,CURLINFO_HEADER_SIZE);
        $this->_parse_header(substr($response,0,$header_size));
        $this->nonce = (array_key_exists('replay-nonce',$this->header))?$this->header['replay-nonce']:null;
        print_r($this->header);
        if ($this->header['content-type'] === 'application/json')
        {
            $this->body = json_decode(substr($response,$header_size),true);
            print_r($this->body);
        }
        else
            $this->body = substr($response,$header_size);
        curl_close($handle);
    }

    public function code()
    {
        return $this->code;
    }

    public function header($name)
    {
        return $this->header[strtolower($name)] ?? null;
    }

    public function body()
    {
        return $this->body;
    }

    public function register($infos)
    {
        $request = array(
            'resource' => 'new-reg',
            'contact' => $infos);
        $this->_post($this->_url('new-reg'),$this->_sign($request));
        $url = $this->header['location'];
        $request = array(
            'resource' => 'reg',
            'contact' => $infos
        );
        $this->_post($url,$this->_sign($request));
        if (array_key_exists('link',$this->header))
        {
            if (is_array($this->header['link']))
            {
                foreach ($this->header['link'] as $link)
                {
                    if (preg_match('|^<([^>]+)>;rel="([^"]+)"$|',$link,$matches) && ($matches[2] === 'terms-of-service'))
                    {
                        $request['agreement'] = $matches[1];
                        break;
                    }
                }
            }
            elseif (preg_match('|^<([^>]+)>;rel="([^"]+)"$|',$this->header['link'],$matches) && ($matches[2] === 'terms-of-service'))
                $request['agreement'] = $matches[1];
            $this->_post($url,$this->_sign($request));
        }
    }

    public function authorize($domain)
    {
        $request = array(
            'resource' => 'new-authz',
            'identifier' => array(
                'type' => 'dns',
                'value' => $domain
            )
        );
        $this->_post($this->_url('new-authz'),$this->_sign($request));
//        return DateTime::createFromFormat('Y-m-d\TH:i:s\.') validité de l'autorisation
    }

    public function getChallengesList()
    {
        if (($this->code === 201) && array_key_exists('challenges',$this->body))
            return $this->body['challenges'];
        return array();
    }

    public function getChallenge($type)
    {
        if (($this->code !== 201) || !array_key_exists('challenges',$this->body))
            throw new Exception('no challenge have been given');
        foreach ($this->body['challenges'] as $challenge)
        {
            if ($challenge['type'] === $type)
                break;
        }
        if ($challenge['type'] !== $type)
            throw new Exception('no challenge of that type');
        $details = openssl_pkey_get_details($this->key);
        $challenge['keyAuthorization'] = $challenge['token'].'.'.self::ACMEBase64url(hash('sha256',sprintf('{"e":"%s","kty":"RSA","n":"%s"}',self::ACMEBase64url($details['rsa']['e']),self::ACMEBase64url($details['rsa']['n'])),true));
        return $challenge;
    }

    public function faceChallenge($challenge)
    {
        $request = array(
            'resource' => 'challenge',
            'type' => $challenge['type'],
            'keyAuthorization' => $challenge['keyAuthorization']
        );
        $this->_post($challenge['uri'],$this->_sign($request));
    }

    public function challengeStatus($uri)
    {
        $this->_get($uri);
        return $this->body;
    }

    /**
     * issue a new cert from a PEM format Certificate Signing Request
     **/
    public function issue($csr)
    {
        if (preg_match('|^[^\r\n]+[\r\n]+([a-z0-9/+]+(?:[\r\n]+[a-z0-9/+]+)*)|mi',$csr,$matches))
        {
            $request = array(
                'resource' => 'new-cert',
                'csr' => strtr($matches[1],'+/','-_')
            );
            $this->_post($this->_url('new-cert'),$this->_sign($request),array('Accept: application/pkix-cert'));
        }
        else
            throw new Exception('error converting CSR PEM encoding to special base64url ACME encoding.');
    }

    public function getCertificate($uri)
    {
        $this->_get($uri,array('Accept: application/pkix-cert'));
    }

    public function revoke($crt)
    {
        if (preg_match('|^[^\r\n]+[\r\n]+([a-z0-9/+]+(?:[\r\n]+[a-z0-9/+]+)*)|mi',$crt,$matches))
        {
            $request = ['resource' => 'revoke-cert','certificate' => strtr($matches[1],'+/','-_')];
            $this->_post($this->_url('revoke-cert'),$this->_sign($request));
        }
        else
            throw new Exception('error converting certificate PEM encoding to special base64url ACME encoding.');
    }
}

#18

The CSR in that JSON decoded weird on my machine:

unable to load X509 request
140094416893592:error:0D07209B:asn1 encoding routines:ASN1_get_object:too long:asn1_lib.c:147:
140094416893592:error:0D068066:asn1 encoding routines:ASN1_CHECK_TLEN:bad object header:tasn_dec.c:1185:
140094416893592:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:tasn_dec.c:374:Type=X509_REQ
140094416893592:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:pem_oth.c:83:

IDK if the base64 urlencode stuff is the same but im doing mine with this:

    private function base64UrlSafeEncode($input)
    {
        //return base64_encode($input);
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

Edit / Update:

# base64 -d mine
{"resource":"new-cert","csr":"MIIC-DCCAeACAQAwKDEmMCQGA1UEAwwdcmVzZXJ2YXRpb24uc2VjdXJlb2JzY3VyZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzsLRW4GzpgdTNdAU
# base64 -d yours
{"resource":"new-cert","csr":"MIIC9DCCAd4CAQAwKDEmMCQGA1UEAwwdcmVzZXJ2YXRpb24udmFsbGVlLW11bnN0\r\nZXIuZXUwggEgMAsGCSqGSIb3DQEBAQOCAQ8AMIIBCgKCAQEAr2vftzpsfl3ryt44\r\n

One other difference I spotted is that you have alot of \r\n characters in the base64, I think I removed that on all of mine? Regarding why one works but another fails I am wondering if there is some subtle encoding logic that doesnt work the same with very specific bit sequences in the REQ?


#19

That was it ! Thanks a lot metaclassing.

I just removed all the \r\n from de base64 CSR and it works.
I still do not understand why it was working with only one name but…


#20

Hi Ingenie

A couple of other points.

A) You are signing a String so the /r/n will create a different signature. I ran into this problem with windows and powershell.
B) I use a template to configure SAN Certificates with Open SSL (attached).
syntax - **openssl -req -new -config sample.txt -key -out sample.csr
you can output to stdout to use with scripts or use a file intermediary (doesn’t make much difference)
C) Double check your SAN. I am not completely sure but your subjectaltname should not be the same as your CN.

SAMPLE.txt (917 Bytes)