How to get a openssl genrsa
compatible key out of the private_key.json
which the official letsencrypt client creates (i.e., for using in e.g. https://github.com/diafygi/acme-tiny/)?
For Example with Java:
final Decoder dec = java.util.Base64.getUrlDecoder(); final Pattern pattern = Pattern.compile("\\{\"e\": \"(.*)\", \"d\": \"(.*)\", \"n\": \"(.*)\", \"q\": \"(.*)\", \"p\": \"(.*)\", \"kty\": \"(.*)\", \"qi\": \"(.*)\", \"dp\": \"(.*)\", \"dq\": \"(.*)\"}"); final String alias = "letsencryptUserAlias"; final String keyFile = argc[0]; final String storeFile = argc[1]; final File f = new File(keyFile); final byte[] b = new byte[(int)f.length()]; try(InputStream s = new FileInputStream(f)) { s.read(b, 0, b.length); } final String json = new String(b); final Matcher m6 = pattern.matcher(json); if(!m6.matches()) throw new IllegalArgumentException(); final BigInteger e = new BigInteger(1, dec.decode(m6.group(1))); final BigInteger d = new BigInteger(1, dec.decode(m6.group(2))); final BigInteger n = new BigInteger(1, dec.decode(m6.group(3))); final BigInteger q = new BigInteger(1, dec.decode(m6.group(4))); final BigInteger p = new BigInteger(1, dec.decode(m6.group(5))); final BigInteger qi = new BigInteger(1, dec.decode(m6.group(7))); final BigInteger dp = new BigInteger(1, dec.decode(m6.group(8))); final BigInteger dq = new BigInteger(1, dec.decode(m6.group(9))); final KeyFactory kf = KeyFactory.getInstance("RSA"); final RSAPrivateCrtKey s_key = (RSAPrivateCrtKey)kf.generatePrivate(new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, qi));
@tlussnig Thanks!
Here my full adapted source:
public static void main(String[] args) throws FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
final Decoder dec = java.util.Base64.getUrlDecoder();
final Pattern pattern = Pattern.compile("\\{\"e\": \"(.*)\", \"d\": \"(.*)\", \"n\": \"(.*)\", \"q\": \"(.*)\", \"p\": \"(.*)\", \"kty\": \"(.*)\", \"qi\": \"(.*)\", \"dp\": \"(.*)\", \"dq\": \"(.*)\"}");
final String keyFile = args[0];
final File f = new File(keyFile);
final byte[] b = new byte[(int)f.length()];
try(InputStream s = new FileInputStream(f)) { s.read(b, 0, b.length); }
final String json = new String(b);
final Matcher m6 = pattern.matcher(json);
if(!m6.matches()) throw new IllegalArgumentException();
final BigInteger e = new BigInteger(1, dec.decode(m6.group(1)));
final BigInteger d = new BigInteger(1, dec.decode(m6.group(2)));
final BigInteger n = new BigInteger(1, dec.decode(m6.group(3)));
final BigInteger q = new BigInteger(1, dec.decode(m6.group(4)));
final BigInteger p = new BigInteger(1, dec.decode(m6.group(5)));
final BigInteger qi = new BigInteger(1, dec.decode(m6.group(7)));
final BigInteger dp = new BigInteger(1, dec.decode(m6.group(8)));
final BigInteger dq = new BigInteger(1, dec.decode(m6.group(9)));
final KeyFactory kf = KeyFactory.getInstance("RSA");
final RSAPrivateCrtKey s_key = (RSAPrivateCrtKey)kf.generatePrivate(new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, qi));
FileWriter o = new FileWriter(new File(args[1]));
final String encoded = Base64.getEncoder().encodeToString(s_key.getEncoded());
o.write(encoded);
o.close();
}
Hate to completely out myself as a java noob, but how on earth does one actually use this code? javac wonāt compile it. It doesnāt run directly via the ājavaā command (saved in a file and passed as an argument). Can those of us that arenāt java devs please get some hints?
Clues are much appreciated.
Thanks,
-C
Iām not sure about the Java code, but if you have Go installed, you can try this program to do a similar thing.
$ go get -u gopkg.in/square/go-jose.v2/...
$ go run certbot-to-pem.go [path to private_key.json]
-----BEGIN RSA PRIVATE KEY-----
<snip>
Here is the program (certbot-to-pem.go
):
package main
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"reflect"
"gopkg.in/square/go-jose.v2"
)
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: certbot-to-pem path-to-certbot-private_key.json")
os.Exit(1)
}
pkBuf, err := ioutil.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
var k jose.JSONWebKey
if err := k.UnmarshalJSON(pkBuf); err != nil {
panic(err)
}
switch p := k.Key.(type) {
case *rsa.PrivateKey:
fmt.Println(string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(p),
})))
default:
panic("Don't know how to deal with " + reflect.TypeOf(p).String())
}
}
Awesome - thank you for the go code. I was able to extract a valid RSA key out of the private_key.json file. Saved this off to a private_key.pem file and openssl reports āRSA key okā when I run a ā-checkā against it.
Now Iām trying to sort out why the key extracted is being reported by openssl as not matching the certificate I was issued by letsencrypt. Am I just not caffeinated enough and completely missing how this private key is used in letsencrypt?
End result I need is both the private key and certificate available so that I can use them in a slapd instance.
Thatās your Letās Encrypt account key. It isnāt used for certificates.
Your certificate private key is present as a symlink in /etc/letsencrypt/live/{domain}/privkey.pem
.
Gotcha - thanks. Thatās where I know they get stored on my other nodes. This system in particular though never set that dir structure up when the letsencrypt provisioning ran:
=====
get a new cert
letsencrypt certonly --standalone --csr ā$WORKDIR/httpd-csr.derā --email ā$EMAILā --agree-tos
Itās a FreeIPA server and someone had contributed a routine to handle generation and installation of letsencrypt certs for the various IPA services:
https://github.com/freeipa/freeipa-letsencrypt.git
Thatās why I was searching around to see if I could figure out some other place that the letsencrypt utility had perhaps put the private key and had for some reason thought it was being stored in the private_key.json file, but I now follow the difference. Iāll re-run things via the CLI and get the proper dir structure working.
Thanks again for the help and enlightenment. Been staring at this for too long so obviously was getting confused.
-Chris
When Certbot is run with the ā--csr
ā option, it doesnāt set things up in the typical /etc/letsencrypt/live/
location.
Thatās why the renew-le.sh
program is copying the certificate from a ā0000_cert.pem
ā file somewhere.
The CSR was generated by certutil
. Iām not familiar with certutil, but I assume it either read a private key from somewhere, or generated one and saved it somewhere.
You should consult the configuration or documentation for FreeIPA or certutil to try to determine where the key is.
Edit: I guess itās in /etc/httpd/alias/
ā possibly not in a .pem
file ā but Iām really not sure.
In the acme-tiny documentation, thereās also a way to do the conversion with Python: https://github.com/diafygi/acme-tiny/#use-existing-lets-encrypt-key