fix: add missing timecondition during init process
[gigi.git] / util-testing / club / wpia / gigi / util / SimpleSigner.java
1 package club.wpia.gigi.util;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.InputStreamReader;
11 import java.io.PrintWriter;
12 import java.io.Reader;
13 import java.math.BigInteger;
14 import java.nio.file.Paths;
15 import java.security.GeneralSecurityException;
16 import java.security.KeyFactory;
17 import java.security.NoSuchAlgorithmException;
18 import java.security.PrivateKey;
19 import java.security.PublicKey;
20 import java.security.Signature;
21 import java.security.cert.CertificateFactory;
22 import java.security.cert.X509Certificate;
23 import java.security.spec.InvalidKeySpecException;
24 import java.security.spec.PKCS8EncodedKeySpec;
25 import java.sql.SQLException;
26 import java.sql.Timestamp;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.Base64;
30 import java.util.Calendar;
31 import java.util.Date;
32 import java.util.GregorianCalendar;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.Properties;
39 import java.util.TimeZone;
40
41 import javax.security.auth.x500.X500Principal;
42
43 import club.wpia.gigi.GigiApiException;
44 import club.wpia.gigi.crypto.SPKAC;
45 import club.wpia.gigi.database.DatabaseConnection;
46 import club.wpia.gigi.database.DatabaseConnection.Link;
47 import club.wpia.gigi.database.GigiPreparedStatement;
48 import club.wpia.gigi.database.GigiResultSet;
49 import club.wpia.gigi.dbObjects.Certificate;
50 import club.wpia.gigi.dbObjects.Certificate.AttachmentType;
51 import club.wpia.gigi.dbObjects.Certificate.CSRType;
52 import club.wpia.gigi.dbObjects.Certificate.SANType;
53 import club.wpia.gigi.dbObjects.Certificate.SubjectAlternateName;
54 import club.wpia.gigi.dbObjects.CertificateProfile;
55 import club.wpia.gigi.dbObjects.Digest;
56 import club.wpia.gigi.output.DateSelector;
57 import club.wpia.gigi.util.ServerConstants.Host;
58 import sun.security.pkcs10.PKCS10;
59 import sun.security.util.DerOutputStream;
60 import sun.security.util.DerValue;
61 import sun.security.util.ObjectIdentifier;
62 import sun.security.x509.AVA;
63 import sun.security.x509.AlgorithmId;
64 import sun.security.x509.GeneralNameInterface;
65 import sun.security.x509.RDN;
66 import sun.security.x509.X500Name;
67
68 public class SimpleSigner {
69
70     private static GigiPreparedStatement warnMail;
71
72     private static GigiPreparedStatement updateMail;
73
74     private static GigiPreparedStatement readyCerts;
75
76     private static GigiPreparedStatement getSANSs;
77
78     private static GigiPreparedStatement revoke;
79
80     private static GigiPreparedStatement revokeCompleted;
81
82     private static GigiPreparedStatement finishJob;
83
84     private static GigiPreparedStatement locateCA;
85
86     private static volatile boolean running = true;
87
88     private static Thread runner;
89
90     private static SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss'Z'");
91
92     static {
93         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
94         sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
95     }
96
97     public static void main(String[] args) throws IOException, SQLException, InterruptedException {
98         Properties p = new Properties();
99         try (Reader reader = new InputStreamReader(new FileInputStream("config/gigi.properties"), "UTF-8")) {
100             p.load(reader);
101         }
102         ServerConstants.init(p);
103         TimeConditions.init(p);
104         DatabaseConnection.init(p);
105
106         runSigner();
107     }
108
109     public static void stopSigner() throws InterruptedException {
110         Thread capturedRunner;
111         synchronized (SimpleSigner.class) {
112             if (runner == null) {
113                 throw new IllegalStateException("already stopped");
114             }
115             capturedRunner = runner;
116             running = false;
117             SimpleSigner.class.notifyAll();
118         }
119         capturedRunner.join();
120     }
121
122     public synchronized static void runSigner() throws SQLException, IOException, InterruptedException {
123         if (runner != null) {
124             throw new IllegalStateException("already running");
125         }
126         running = true;
127
128         runner = new Thread() {
129
130             @Override
131             public void run() {
132                 try (Link l = DatabaseConnection.newLink(false)) {
133                     readyCerts = new GigiPreparedStatement("SELECT certs.id AS id, jobs.id AS jobid, csr_type, md, `executeFrom`, `executeTo`, profile FROM jobs " + //
134                             "INNER JOIN certs ON certs.id=jobs.`targetId` " + //
135                             "INNER JOIN profiles ON profiles.id=certs.profile " + //
136                             "WHERE jobs.state='open' " + //
137                             "AND task='sign'");
138
139                     getSANSs = new GigiPreparedStatement("SELECT contents, type FROM `subjectAlternativeNames` " + //
140                             "WHERE `certId`=?");
141
142                     updateMail = new GigiPreparedStatement("UPDATE certs SET created=NOW(), serial=?, caid=?, expire=? WHERE id=?");
143                     warnMail = new GigiPreparedStatement("UPDATE jobs SET attempt=attempt+1, state=CASE WHEN attempt<3 THEN 'open'::`jobState` ELSE 'error'::`jobState` END WHERE id=?");
144
145                     revoke = new GigiPreparedStatement("SELECT certs.id, jobs.id FROM jobs INNER JOIN certs ON jobs.`targetId`=certs.id" + " WHERE jobs.state='open' AND task='revoke'");
146                     revokeCompleted = new GigiPreparedStatement("UPDATE `certs` SET revoked=NOW() WHERE id=?");
147
148                     finishJob = new GigiPreparedStatement("UPDATE jobs SET state='done' WHERE id=?");
149
150                     locateCA = new GigiPreparedStatement("SELECT id FROM cacerts WHERE keyname=?");
151
152                     work();
153                 } catch (InterruptedException e) {
154                     throw new Error(e);
155                 }
156             }
157
158         };
159         runner.start();
160     }
161
162     public static void ping() {
163         synchronized (SimpleSigner.class) {
164             SimpleSigner.class.notifyAll();
165             try {
166                 SimpleSigner.class.wait(2000);
167             } catch (InterruptedException e) {
168                 e.printStackTrace();
169             }
170         }
171     }
172
173     private synchronized static void work() {
174         try {
175             gencrl();
176         } catch (IOException e2) {
177             e2.printStackTrace();
178         } catch (InterruptedException e2) {
179             e2.printStackTrace();
180         }
181
182         while (running) {
183             try {
184                 signCertificates();
185                 revokeCertificates();
186
187                 SimpleSigner.class.notifyAll();
188                 SimpleSigner.class.wait(5000);
189             } catch (IOException e) {
190                 e.printStackTrace();
191             } catch (SQLException e) {
192                 e.printStackTrace();
193             } catch (InterruptedException e1) {
194             }
195         }
196         runner = null;
197     }
198
199     private static void revokeCertificates() throws SQLException, IOException, InterruptedException {
200         GigiResultSet rs = revoke.executeQuery();
201         boolean worked = false;
202         while (rs.next()) {
203             int id = rs.getInt(1);
204             worked = true;
205             System.out.println("Revoke faked: " + id);
206             revokeCompleted.setInt(1, id);
207             revokeCompleted.executeUpdate();
208             finishJob.setInt(1, rs.getInt(2));
209             finishJob.executeUpdate();
210         }
211         if (worked) {
212             gencrl();
213         }
214     }
215
216     private static void gencrl() throws IOException, InterruptedException {
217         if (true) {
218             return;
219         }
220         String[] call = new String[] {
221                 "openssl",
222                 "ca",//
223                 "-cert",
224                 "../unassured.crt",//
225                 "-keyfile",
226                 "../unassured.key",//
227                 "-gencrl",//
228                 "-crlhours",//
229                 "12",//
230                 "-out",
231                 "../unassured.crl",//
232                 "-config",
233                 "../selfsign.config"
234
235         };
236         Process p1 = Runtime.getRuntime().exec(call, null, new File("keys/unassured.ca"));
237         if (p1.waitFor() != 0) {
238             System.out.println("Error while generating crl.");
239         }
240     }
241
242     private static void signCertificates() throws SQLException {
243         GigiResultSet rs = readyCerts.executeQuery();
244
245         Calendar c = Calendar.getInstance();
246         c.setTimeZone(TimeZone.getTimeZone("UTC"));
247         while (rs.next()) {
248             int id = rs.getInt("id");
249             System.out.println("sign: " + id);
250             try {
251                 Certificate crt = Certificate.getById(id);
252                 String csrType = rs.getString("csr_type");
253                 CSRType ct = CSRType.valueOf(csrType);
254
255                 Timestamp from = rs.getTimestamp("executeFrom");
256                 String length = rs.getString("executeTo");
257                 Date fromDate;
258                 Date toDate;
259                 if (from == null) {
260                     fromDate = new Date(System.currentTimeMillis());
261                 } else {
262                     fromDate = new Date(from.getTime());
263                 }
264                 if (length.endsWith("m") || length.endsWith("y")) {
265                     String num = length.substring(0, length.length() - 1);
266                     int inter = Integer.parseInt(num);
267                     c.setTime(fromDate);
268                     if (length.endsWith("m")) {
269                         c.add(Calendar.MONTH, inter);
270                     } else {
271                         c.add(Calendar.YEAR, inter);
272                     }
273                     toDate = c.getTime();
274                 } else {
275                     toDate = DateSelector.getDateFormat().parse(length);
276                 }
277
278                 getSANSs.setInt(1, id);
279                 GigiResultSet san = getSANSs.executeQuery();
280
281                 LinkedList<SubjectAlternateName> altnames = new LinkedList<>();
282                 while (san.next()) {
283                     altnames.add(new SubjectAlternateName(SANType.valueOf(san.getString("type").toUpperCase()), san.getString("contents")));
284                 }
285                 // TODO look them up!
286                 // cfg.println("keyUsage=critical," +
287                 // "digitalSignature, keyEncipherment, keyAgreement");
288                 // cfg.println("extendedKeyUsage=critical," + "clientAuth");
289                 // cfg.close();
290
291                 int profile = rs.getInt("profile");
292                 CertificateProfile cp = CertificateProfile.getById(profile);
293                 String s = cp.getId() + "";
294                 while (s.length() < 4) {
295                     s = "0" + s;
296                 }
297                 s += "-" + cp.getKeyName() + ".cfg";
298                 Properties caP = new Properties();
299                 try (FileInputStream inStream = new FileInputStream("signer/profiles/" + s)) {
300                     caP.load(inStream);
301                 }
302
303                 HashMap<String, String> subj = new HashMap<>();
304                 try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT name, value FROM `certAvas` WHERE `certId`=?")) {
305                     ps.setInt(1, rs.getInt("id"));
306                     GigiResultSet rs2 = ps.executeQuery();
307                     while (rs2.next()) {
308                         String name = rs2.getString("name");
309                         if (name.equals("EMAIL")) {
310                             name = "emailAddress";
311                         }
312                         subj.put(name, rs2.getString("value"));
313                     }
314                 }
315                 if (subj.size() == 0) {
316                     subj.put("CN", "<empty>");
317                     System.out.println("WARNING: DN was empty");
318                 }
319                 System.out.println(subj);
320
321                 PublicKey pk;
322                 byte[] data = crt.getAttachment(AttachmentType.CSR).getBytes("UTF-8");
323                 if (ct == CSRType.SPKAC) {
324                     String dt = new String(data, "UTF-8");
325                     if (dt.startsWith("SPKAC=")) {
326                         dt = dt.substring(6);
327                         data = dt.getBytes("UTF-8");
328                         System.out.println(dt);
329                     }
330                     SPKAC sp = new SPKAC(Base64.getDecoder().decode(data));
331                     pk = sp.getPubkey();
332                 } else {
333                     PKCS10 p10 = new PKCS10(PEM.decode("(NEW )?CERTIFICATE REQUEST", new String(data, "UTF-8")));
334                     pk = p10.getSubjectPublicKeyInfo();
335                 }
336                 Calendar cal = GregorianCalendar.getInstance();
337                 String ca = caP.getProperty("ca") + "_" + cal.get(Calendar.YEAR) + (cal.get(Calendar.MONTH) >= 6 ? "_2" : "_1");
338                 File parent = new File("signer/ca");
339                 File[] caFiles = parent.listFiles();
340                 if (null == caFiles) {
341                     caFiles = new File[0];
342                 }
343                 if ( !new File(parent, ca).exists()) {
344                     System.out.println("CA " + ca + " not found. Searching for anything other remotely fitting.");
345                     for (File f : caFiles) {
346                         if (f.getName().startsWith(caP.getProperty("ca"))) {
347                             ca = f.getName();
348                             break;
349                         }
350                     }
351                 }
352                 File caKey = new File(parent, ca + "/ca.key");
353                 PrivateKey i = loadOpensslKey(caKey);
354
355                 X509Certificate root = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(new FileInputStream("signer/ca/" + ca + "/ca.crt"));
356                 byte[] cert = generateCert(pk, i, subj, root.getSubjectX500Principal(), altnames, fromDate, toDate, Digest.valueOf(rs.getString("md").toUpperCase()), caP.getProperty("eku"));
357                 StringBuilder b = new StringBuilder();
358                 b.append("-----BEGIN CERTIFICATE-----\r\n");
359                 b.append(Base64.getMimeEncoder().encodeToString(cert));
360                 b.append("-----END CERTIFICATE-----\r\n");
361                 crt.addAttachment(AttachmentType.CRT, b.toString());
362
363                 try (InputStream is = new ByteArrayInputStream(cert)) {
364                     locateCA.setString(1, ca);
365                     GigiResultSet caRs = locateCA.executeQuery();
366                     if ( !caRs.next()) {
367                         throw new Error("ca " + ca + " was not found");
368                     }
369
370                     CertificateFactory cf = CertificateFactory.getInstance("X.509");
371                     X509Certificate crtp = (X509Certificate) cf.generateCertificate(is);
372                     BigInteger serial = crtp.getSerialNumber();
373                     updateMail.setString(1, serial.toString(16));
374                     updateMail.setInt(2, caRs.getInt("id"));
375                     updateMail.setTimestamp(3, new Timestamp(toDate.getTime()));
376                     updateMail.setInt(4, id);
377                     updateMail.executeUpdate();
378
379                     finishJob.setInt(1, rs.getInt("jobid"));
380                     finishJob.executeUpdate();
381                     System.out.println("signed: " + id);
382                     continue;
383                 }
384
385             } catch (GeneralSecurityException e) {
386                 e.printStackTrace();
387             } catch (IOException e) {
388                 e.printStackTrace();
389             } catch (ParseException e) {
390                 e.printStackTrace();
391             } catch (GigiApiException e) {
392                 e.printStackTrace();
393             }
394             System.out.println("Error with: " + id);
395             warnMail.setInt(1, rs.getInt("jobid"));
396             warnMail.executeUpdate();
397
398         }
399         rs.close();
400     }
401
402     private static PrivateKey loadOpensslKey(File f) throws FileNotFoundException, IOException, InvalidKeySpecException, NoSuchAlgorithmException {
403         byte[] p8b = PEM.decode("RSA PRIVATE KEY", new String(IOUtils.readURL(new FileInputStream(f))));
404         DerOutputStream dos = new DerOutputStream();
405         dos.putInteger(0);
406         new AlgorithmId(new ObjectIdentifier(new int[] {
407                 1, 2, 840, 113549, 1, 1, 1
408         })).encode(dos);
409         dos.putOctetString(p8b);
410         byte[] ctx = dos.toByteArray();
411         dos.reset();
412         dos.write(DerValue.tag_Sequence, ctx);
413         PKCS8EncodedKeySpec p8 = new PKCS8EncodedKeySpec(dos.toByteArray());
414         PrivateKey i = KeyFactory.getInstance("RSA").generatePrivate(p8);
415         return i;
416     }
417
418     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 {
419         File f = Paths.get("signer", "serial").toFile();
420         if ( !f.exists()) {
421             try (FileOutputStream fos = new FileOutputStream(f)) {
422                 fos.write("1".getBytes("UTF-8"));
423             }
424         }
425         try (FileInputStream fr = new FileInputStream(f)) {
426             byte[] serial = IOUtils.readURL(fr);
427             BigInteger ser = new BigInteger(new String(serial).trim());
428             ser = ser.add(BigInteger.ONE);
429
430             PrintWriter pw = new PrintWriter(f);
431             pw.println(ser);
432             pw.close();
433             if (digest != Digest.SHA256 && digest != Digest.SHA384 && digest != Digest.SHA512) {
434                 System.err.println("assuming sha256 either way ;-): " + digest);
435                 digest = Digest.SHA256;
436             }
437             ObjectIdentifier sha512withrsa = new ObjectIdentifier(new int[] {
438                     1, 2, 840, 113549, 1, 1, digest == Digest.SHA256 ? 11 : (digest == Digest.SHA384 ? 12 : 13)
439             });
440             AlgorithmId aid = new AlgorithmId(sha512withrsa);
441             Signature s = Signature.getInstance(digest == Digest.SHA256 ? "SHA256withRSA" : (digest == Digest.SHA384 ? "SHA384withRSA" : "SHA512withRSA"));
442
443             DerOutputStream cert = new DerOutputStream();
444             DerOutputStream content = new DerOutputStream();
445             {
446                 DerOutputStream version = new DerOutputStream();
447                 version.putInteger(2); // v3
448                 content.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 0), version);
449             }
450             content.putInteger(ser); // Serial
451             aid.encode(content);
452
453             {
454                 content.write(issuer.getEncoded());
455             }
456             {
457                 DerOutputStream notAround = new DerOutputStream();
458                 notAround.putUTCTime(fromDate);
459                 notAround.putUTCTime(toDate);
460                 content.write(DerValue.tag_Sequence, notAround);
461             }
462             {
463
464                 X500Name xn = genX500Name(subj);
465                 content.write(xn.getEncoded());
466             }
467             {
468                 content.write(pk.getEncoded());
469             }
470             {
471                 DerOutputStream extensions = new DerOutputStream();
472                 {
473                     addExtension(extensions, new ObjectIdentifier(new int[] {
474                             2, 5, 29, 17
475                     }), generateSAN(altnames));
476                     addExtension(extensions, new ObjectIdentifier(new int[] {
477                             2, 5, 29, 15
478                     }), generateKU());
479                     addExtension(extensions, new ObjectIdentifier(new int[] {
480                             2, 5, 29, 37
481                     }), generateEKU(eku));
482                     addExtension(extensions, new ObjectIdentifier(new int[] {
483                             1, 3, 6, 1, 5, 5, 7, 1, 1
484                     }), generateAIA());
485                 }
486                 DerOutputStream extensionsSeq = new DerOutputStream();
487                 extensionsSeq.write(DerValue.tag_Sequence, extensions);
488                 content.write(DerValue.createTag(DerValue.TAG_CONTEXT, true, (byte) 3), extensionsSeq);
489             }
490
491             DerOutputStream contentSeq = new DerOutputStream();
492
493             contentSeq.write(DerValue.tag_Sequence, content.toByteArray());
494
495             s.initSign(prk);
496             s.update(contentSeq.toByteArray());
497
498             aid.encode(contentSeq);
499             contentSeq.putBitString(s.sign());
500             cert.write(DerValue.tag_Sequence, contentSeq);
501
502             // X509Certificate c = (X509Certificate)
503             // CertificateFactory.getInstance("X509").generateCertificate(new
504             // ByteArrayInputStream(cert.toByteArray()));
505             // c.verify(pk); only for self-signeds
506
507             byte[] res = cert.toByteArray();
508             cert.close();
509             return res;
510         }
511
512     }
513
514     private static byte[] generateAIA() throws IOException {
515         try (DerOutputStream dos = new DerOutputStream()) {
516             try (DerOutputStream seq = new DerOutputStream()) {
517                 seq.putOID(new ObjectIdentifier(new int[] {
518                         1, 3, 6, 1, 5, 5, 7, 48, 2
519                 }));
520                 seq.write((byte) 0x86, ("http://" + ServerConstants.getHostName(Host.OCSP_RESPONDER)).getBytes("UTF-8"));
521                 dos.write(DerValue.tag_Sequence, seq);
522             }
523             byte[] data = dos.toByteArray();
524             dos.reset();
525             dos.write(DerValue.tag_Sequence, data);
526             return dos.toByteArray();
527         }
528     }
529
530     private static byte[] generateKU() throws IOException {
531         try (DerOutputStream dos = new DerOutputStream()) {
532             dos.putBitString(new byte[] {
533                     (byte) 0b10101000
534             });
535             return dos.toByteArray();
536         }
537     }
538
539     private static byte[] generateEKU(String eku) throws IOException {
540
541         try (DerOutputStream dos = new DerOutputStream()) {
542             for (String name : eku.split(",")) {
543                 name = name.trim();
544                 ObjectIdentifier oid;
545                 switch (name) {
546                 case "serverAuth":
547                     oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.1");
548                     break;
549                 case "clientAuth":
550                     oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.2");
551                     break;
552                 case "codeSigning":
553                     oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.3");
554                     break;
555                 case "emailProtection":
556                     oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.4");
557                     break;
558                 case "OCSPSigning":
559                     oid = new ObjectIdentifier("1.3.6.1.5.5.7.3.9");
560                     break;
561
562                 default:
563                     throw new Error(name);
564                 }
565                 dos.putOID(oid);
566             }
567             byte[] data = dos.toByteArray();
568             dos.reset();
569             dos.write(DerValue.tag_Sequence, data);
570             return dos.toByteArray();
571         }
572     }
573
574     public static X500Name genX500Name(Map<String, String> subj) throws IOException {
575         LinkedList<RDN> rdns = new LinkedList<>();
576         for (Entry<String, String> i : subj.entrySet()) {
577             RDN rdn = genRDN(i);
578             rdns.add(rdn);
579         }
580         return new X500Name(rdns.toArray(new RDN[rdns.size()]));
581     }
582
583     private static RDN genRDN(Entry<String, String> i) throws IOException {
584         DerOutputStream dos = new DerOutputStream();
585         dos.putUTF8String(i.getValue());
586         int[] oid;
587         String key = i.getKey();
588         switch (key) {
589         case "CN":
590             oid = new int[] {
591                     2, 5, 4, 3
592             };
593             break;
594         case "EMAIL":
595         case "emailAddress":
596             oid = new int[] {
597                     1, 2, 840, 113549, 1, 9, 1
598             };
599             break;
600         case "O":
601             oid = new int[] {
602                     2, 5, 4, 10
603             };
604             break;
605         case "OU":
606             oid = new int[] {
607                     2, 5, 4, 11
608             };
609             break;
610         case "ST":
611             oid = new int[] {
612                     2, 5, 4, 8
613             };
614             break;
615         case "L":
616             oid = new int[] {
617                     2, 5, 4, 7
618             };
619             break;
620         case "C":
621             oid = new int[] {
622                     2, 5, 4, 6
623             };
624             break;
625         default:
626             dos.close();
627             throw new Error("unknown RDN-type: " + key);
628         }
629         RDN rdn = new RDN(new AVA(new ObjectIdentifier(oid), new DerValue(dos.toByteArray())));
630         dos.close();
631         return rdn;
632     }
633
634     private static void addExtension(DerOutputStream extensions, ObjectIdentifier oid, byte[] extContent) throws IOException {
635         DerOutputStream SANs = new DerOutputStream();
636         SANs.putOID(oid);
637         SANs.putOctetString(extContent);
638
639         extensions.write(DerValue.tag_Sequence, SANs);
640     }
641
642     private static byte[] generateSAN(List<SubjectAlternateName> altnames) throws IOException {
643         DerOutputStream SANContent = new DerOutputStream();
644         for (SubjectAlternateName san : altnames) {
645             byte type = 0;
646             if (san.getType() == SANType.DNS) {
647                 type = (byte) GeneralNameInterface.NAME_DNS;
648             } else if (san.getType() == SANType.EMAIL) {
649                 type = (byte) GeneralNameInterface.NAME_RFC822;
650             } else {
651                 SANContent.close();
652                 throw new Error("" + san.getType());
653             }
654             SANContent.write(DerValue.createTag(DerValue.TAG_CONTEXT, false, type), san.getName().getBytes("UTF-8"));
655         }
656         DerOutputStream SANSeqContent = new DerOutputStream();
657         SANSeqContent.write(DerValue.tag_Sequence, SANContent);
658         byte[] byteArray = SANSeqContent.toByteArray();
659         SANContent.close();
660         SANSeqContent.close();
661         return byteArray;
662     }
663 }