+
+ private static PrivateKey loadOpensslKey(File f) throws FileNotFoundException, IOException, InvalidKeySpecException, NoSuchAlgorithmException {
+ byte[] p8b = PEM.decode("RSA PRIVATE KEY", new String(IOUtils.readURL(new FileInputStream(f))));
+ DerOutputStream dos = new DerOutputStream();
+ dos.putInteger(0);
+ new AlgorithmId(new ObjectIdentifier(new int[] {
+ 1, 2, 840, 113549, 1, 1, 1
+ })).encode(dos);
+ dos.putOctetString(p8b);
+ byte[] ctx = dos.toByteArray();
+ dos.reset();
+ dos.write(DerValue.tag_Sequence, ctx);
+ PKCS8EncodedKeySpec p8 = new PKCS8EncodedKeySpec(dos.toByteArray());
+ PrivateKey i = KeyFactory.getInstance("RSA").generatePrivate(p8);
+ return i;
+ }
+
+ public static synchronized byte[] generateCert(PublicKey pk, PrivateKey prk, Map<String, String> subj, X500Principal issuer, List<SubjectAlternateName> altnames, Date fromDate, Date toDate, Digest digest, String eku) throws IOException, GeneralSecurityException {
+ File f = Paths.get("signer", "serial").toFile();
+ if ( !f.exists()) {
+ try (FileOutputStream fos = new FileOutputStream(f)) {
+ fos.write("1".getBytes("UTF-8"));
+ }
+ }
+ try (FileInputStream fr = new FileInputStream(f)) {
+ byte[] serial = IOUtils.readURL(fr);
+ BigInteger ser = new BigInteger(new String(serial).trim());
+ ser = ser.add(BigInteger.ONE);
+
+ PrintWriter pw = new PrintWriter(f);
+ pw.println(ser);
+ pw.close();
+ if (digest != Digest.SHA256 && digest != Digest.SHA512) {
+ System.err.println("assuming sha256 either way ;-): " + digest);
+ digest = Digest.SHA256;
+ }
+ ObjectIdentifier sha512withrsa = new ObjectIdentifier(new int[] {
+ 1, 2, 840, 113549, 1, 1, digest == Digest.SHA256 ? 11 : 13
+ });
+ AlgorithmId aid = new AlgorithmId(sha512withrsa);
+ Signature s = Signature.getInstance(digest == Digest.SHA256 ? "SHA256withRSA" : "SHA512withRSA");
+
+ DerOutputStream cert = new DerOutputStream();
+ DerOutputStream content = new DerOutputStream();
+ {
+ DerOutputStream version = new DerOutputStream();
+ version.putInteger(2); // v3
+ content.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), version);
+ }
+ content.putInteger(ser); // Serial
+ aid.encode(content);
+
+ {
+ content.write(issuer.getEncoded());
+ }
+ {
+ DerOutputStream notAround = new DerOutputStream();
+ notAround.putUTCTime(fromDate);
+ notAround.putUTCTime(toDate);
+ content.write(DerValue.tag_Sequence, notAround);
+ }
+ {
+
+ X500Name xn = genX500Name(subj);
+ content.write(xn.getEncoded());
+ }
+ {
+ content.write(pk.getEncoded());
+ }
+ {
+ DerOutputStream extensions = new DerOutputStream();
+ {
+ addExtension(extensions, new ObjectIdentifier(new int[] {
+ 2, 5, 29, 17
+ }), generateSAN(altnames));
+ addExtension(extensions, new ObjectIdentifier(new int[] {
+ 2, 5, 29, 15
+ }), generateKU());
+ addExtension(extensions, new ObjectIdentifier(new int[] {
+ 2, 5, 29, 37
+ }), generateEKU(eku));
+ }
+ DerOutputStream extensionsSeq = new DerOutputStream();
+ extensionsSeq.write(DerValue.tag_Sequence, extensions);
+ content.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 3), extensionsSeq);
+ }
+
+ DerOutputStream contentSeq = new DerOutputStream();
+
+ contentSeq.write(DerValue.tag_Sequence, content.toByteArray());
+
+ s.initSign(prk);
+ s.update(contentSeq.toByteArray());
+
+ aid.encode(contentSeq);
+ contentSeq.putBitString(s.sign());
+ cert.write(DerValue.tag_Sequence, contentSeq);
+
+ // X509Certificate c = (X509Certificate)
+ // CertificateFactory.getInstance("X509").generateCertificate(new
+ // ByteArrayInputStream(cert.toByteArray()));
+ // c.verify(pk); only for self-signeds
+
+ byte[] res = cert.toByteArray();
+ cert.close();
+ return res;
+ }
+
+ }
+
+ private static byte[] generateKU() throws IOException {
+ try (DerOutputStream dos = new DerOutputStream()) {
+ dos.putBitString(new byte[] {
+ (byte) 0b10101000
+ });
+ return dos.toByteArray();
+ }
+ }
+
+ private static byte[] generateEKU(String eku) throws IOException {
+
+ try (DerOutputStream dos = new DerOutputStream()) {
+ for (String name : eku.split(",")) {
+ name = name.trim();
+ ObjectIdentifier oid;
+ switch (name) {
+ case "serverAuth":
+ oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.1");
+ break;
+ case "clientAuth":
+ oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.2");
+ break;
+ case "codeSigning":
+ oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.3");
+ break;
+ case "emailProtection":
+ oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.4");
+ break;
+ case "OCSPSigning":
+ oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.9");
+ break;
+
+ default:
+ throw new Error(name);
+ }
+ dos.putOID(oid);
+ }
+ byte[] data = dos.toByteArray();
+ dos.reset();
+ dos.write(DerValue.tag_Sequence, data);
+ return dos.toByteArray();
+ }
+ }
+
+ public static X500Name genX500Name(Map<String, String> subj) throws IOException {
+ LinkedList<RDN> rdns = new LinkedList<>();
+ for (Entry<String, String> i : subj.entrySet()) {
+ RDN rdn = genRDN(i);
+ rdns.add(rdn);
+ }
+ return new X500Name(rdns.toArray(new RDN[rdns.size()]));
+ }
+
+ private static RDN genRDN(Entry<String, String> i) throws IOException {
+ DerOutputStream dos = new DerOutputStream();
+ dos.putUTF8String(i.getValue());
+ int[] oid;
+ String key = i.getKey();
+ switch (key) {
+ case "CN":
+ oid = new int[] {
+ 2, 5, 4, 3
+ };
+ break;
+ case "EMAIL":
+ case "emailAddress":
+ oid = new int[] {
+ 1, 2, 840, 113549, 1, 9, 1
+ };
+ break;
+ case "O":
+ oid = new int[] {
+ 2, 5, 4, 10
+ };
+ break;
+ case "OU":
+ oid = new int[] {
+ 2, 5, 4, 11
+ };
+ break;
+ case "ST":
+ oid = new int[] {
+ 2, 5, 4, 8
+ };
+ break;
+ case "L":
+ oid = new int[] {
+ 2, 5, 4, 7
+ };
+ break;
+ case "C":
+ oid = new int[] {
+ 2, 5, 4, 6
+ };
+ break;
+ default:
+ dos.close();
+ throw new Error("unknown RDN-type: " + key);
+ }
+ RDN rdn = new RDN(new AVA(new ObjectIdentifier(oid), new DerValue(dos.toByteArray())));
+ dos.close();
+ return rdn;
+ }
+
+ private static void addExtension(DerOutputStream extensions, ObjectIdentifier oid, byte[] extContent) throws IOException {
+ DerOutputStream SANs = new DerOutputStream();
+ SANs.putOID(oid);
+ SANs.putOctetString(extContent);
+
+ extensions.write(DerValue.tag_Sequence, SANs);
+ }
+
+ private static byte[] generateSAN(List<SubjectAlternateName> altnames) throws IOException {
+ DerOutputStream SANContent = new DerOutputStream();
+ for (SubjectAlternateName san : altnames) {
+ byte type = 0;
+ if (san.getType() == SANType.DNS) {
+ type = (byte) GeneralNameInterface.NAME_DNS;
+ } else if (san.getType() == SANType.EMAIL) {
+ type = (byte) GeneralNameInterface.NAME_RFC822;
+ } else {
+ SANContent.close();
+ throw new Error("" + san.getType());
+ }
+ SANContent.write(DerValue.createTag(DerValue.TAG_CONTEXT, false, type), san.getName().getBytes("UTF-8"));
+ }
+ DerOutputStream SANSeqContent = new DerOutputStream();
+ SANSeqContent.write(DerValue.tag_Sequence, SANContent);
+ byte[] byteArray = SANSeqContent.toByteArray();
+ SANContent.close();
+ SANSeqContent.close();
+ return byteArray;
+ }