Archive

For the java category

Using Sequences as Encryption IVs

No Comments

Anyone using low-level encryption libraries should know that you need both a SecretKey and a “random” IV. The easiest way to get a good IV is to use a SecureRandom instance to generate the necessary value.

This approach has one major drawback – it requires you to store or transmit a 16- or even 32-byte value. This is not always a trivial concern. As one example many applications require the user to enter a BASE-32-encoded license key and including a full IV can double the length of the license key. Longer keys can be a real headache when dealing with low-bandwidth channels such as a telephone call to customer support or stickers on optical media – people are more likely to mis-enter the key, you have to use a smaller font which could cause problems for visually impaired users, etc.

Another example is database records. A full IV can significantly increase the size of a record that contains only an integer primary key and an encrypted value – that means fewer records per page. Records/page isn’t a big concern on smaller databases but it matters as you scale up.

What if the full IV could be replaced by a 4-byte counter? That drops 12 bytes per record, or a staggering 20 characters when using BASE-32 encoding.

  • Using the counter directly is insecure. Merging the counter with a salt, e.g. by using XOR or adding it using BigInteger math, is better but still relatively insecure.
  • Encrypting the counter with the same encryption key as the data is also insecure.
  • Encrypting the counter using a separate encryption key is secure. You don’t have to mix the counter with a salt but there’s no downside to it.

The last approach gives us another benefit – it’s a natural way to split the effective encryption key into two pieces that can be stored independently. (E.g., one key is stored on the filesystem outside of the webapp and the other key is provided via the application container using JNDI.) If we use a salt with the counter we have three pieces that can be stored independently.

This gives us a lot of flexibility when using a low-level library like JCE. Higher-level libraries like ESAPI or GPG handle IVs themselves but it’s often possible to explicitly set the IV. For instance the ESAPI CipherSpec class provides a mechanism to set the IV.

Does this mean that we should always use a sequence to generate an IV? No – it requires a little more effort to generate an IV from a sequence than to use a random byte array. You also have to consider the possibility that someone will “improve” the implementation later and use the same encryption key for IV and data, or remove the encryption altogether. However it’s a good tool to have in your arsenal for the times when it is appropriate.

More on Password Encryption

No Comments

I was recently reading Spring Security 3 and it made an interesting point about some LDAP servers.

The servers never provide the (hashed) password.

That raises the immediate question of how you can possibly authenticate the user. The answer is straightforward – the underlying database query becomes

select prop1, prop2, prop3 from users where username='user' and password='hashed.password'

instead of

select * from users where username='user'

This is hard to enforce in a standard database unless you use stored procedures or, maybe, use views and column-based access permissions. The former would work well, the latter would undoubtably be fragile and quietly relaxed by someone who “didn’t see the harm”.

However the benefits of this approach should be obvious. If an attaker is limited to SQL injection the first approach prevents attackers from seeing the hashed passwords (provided the column is properly protected). The second approach does not. Of course this is not a panacea since there are many other attack vectors.

How do you handle hashing?

This approach immediately raises the question of how you handle hashing. There are three immediate possibilities.

Use a system-wide salt. This is the easiest approach but it’s also the weakest.

Use a hybrid username-based/system-wide salt. Salts based on usernames alone are fairly weak since they’re easy to guess. Hybrid salts based on usernames and a system-wide salt are stronger. A minimum salt would be H(username.secret).

Use a random salt. Random salts are the strongest but they require two queries. The first gets the salt by username, the second gets the rest of the data by username and hashed password.

In all cases the standard hashing rules apply. If you use a different app, e.g., LDAP, to manage the authentication information then you should do whatever it expects. If you’re rolling your own you should use a trusted library like bcrypt or modern hashing techniques.

A quick hand-wave of the latter:

  1. byte[] hash(String password, String username, String secret) {
  2.    byte[] salt = crypto.hash(username + secret);
  3.    byte[] hash = crypto.hash(password + salt);
  4.  
  5.    for (int round = 0; round < 1000; round++) {
  6.       hash = crypto.hash(hash ^ salt);
  7.    }
  8.  
  9.    return hash;
  10. }
byte[] hash(String password, String username, String secret) {
   byte[] salt = crypto.hash(username + secret);
   byte[] hash = crypto.hash(password + salt);

   for (int round = 0; round < 1000; round++) {
      hash = crypto.hash(hash ^ salt);
   }

   return hash;
}

The multiple hashes are required since serious attackers now have access to complete rainbow tables and GPU farms.

LDAP

Going back a step – why does the spring security book discuss LDAP?

There are several reasons. First, there are now several good embedded LDAP implementations. It can be a separate .rar in your .ear file, it could even be a single .war file. This should be no more surprising than an embedded database implementation like Jetty or H2.

Second, the implementation has already been written. You have to configure it, of course, but you don’t have to write any code.

Finally, it integrates with enterprise systems. This isn’t an issue with a public-facing site but is very important with internal sites.

PL/Java Code Finally Available

No Comments

About a year ago I published a number of articles on PL/Java:

I had always intended to publish the code but never had the time to clean it up for publication – fleshing out the unit tests, adding the copyright and licensing notices, etc.

No longer – I’ve created a googlecode project for my project. At the moment it only has two user-defined types (Rational and Complex) and the unit tests are far from complete but I’ll flesh it out. Check back in… 10 months! :-)

Google Code Project: PostgreSQL PL/Java examples.

I should point out that there’s a known bug in both UDTs when performing implicit casts. If the first implicit cast is from a string everything works. If the first implicit cast is from an int then all implicit casts are screwed up. I’m following up on this on the pl/java mailing list.

(Sidenote: there’s also a project containing the code I was using in my discussion on digital certificates. It’s much more ambitious and still needs a lot of work but it’s reached the ‘minimally useful’ threshold. Google Code Project: Otter CA.)

What’s On My Desk

No Comments

A few people have asked what’s on my desk since I’m posting (irregularly) on unusual topics. The answer is actually pretty boring. My posts are mostly the result of some lateral thinking and being unable to find any answers in google and stack exchange searches.

Scala

I’m taking the 7 week Functional Programming Principles in Scala course at Coursera. I’m also reading the book Programming In Scala. (I have the first edition.) I’ll probably also pick up Scala in Depth and/or Scala in Action.

As a rule I’ve found that it takes about 6 months in a new language to become familiar with the standard libraries and at least two years to become familiar with the ecosystem. (That’s the difference between being familiar with java.util.* etc and being familiar with Spring/EJB3, hibernate, at least one web framework, plus whatever you need for your specific tasks.)

So do I think I’ll be fluent in Scala in two months? No, of course not, but I should be able to work in the language even if I don’t yet have a good familiarity with, e.g., Play or Akka.

Sidenote: Ruby and JRuby are popular in many shops in the Boulder area.

Information Security

I’m also taking the 10 week Information Security and Risk Management in Context course at Coursera. The Coursera class is free but you can also enroll at the University of Washington for a certification program or for graduate level credit.

I’m still on the fence on this class. I’m a techie but have been studying for PMP and CISSP certs to get a broader perspective (but see below). The first week focused on the role of the CISO (C-level executive for information security – think CEO, CFO, CIO, CTO, etc.) and that’s a bit too far from my world. But we’ll see how the next few classes go.

Do Certificates Have Value?

There are two answers to this question. The less important one is that they can get you past the HR gatekeepers. Today the tech job market is extremely hot but at times in the past, and undoubtably at times in the future, there were far more applicants than positions and the HR gatekeepers would use things like certifications to winnow the resumes. The cert wouldn’t get you the job but it might get you the interview.

The more important answer is that studying for a cert forces you to take a broader view. I’ve never used much of what I learned when studying for my Security+ and java certs… but I did use things that I would have never seen unless I had studied for those exams.

Hence studying for the PMP and CISSP exams. I don’t have the practical experience for either cert but I’ve learned a tremendous amount by studying for them. And who knows – I’m still on the fence about getting a CISSP (Assoc) cert. I could have probably passed an earlier revision of the exam but it’s a moving target.

Prep Work For Next Job

EJB3 in Action. I know the Spring framework very well but some sites use EJB3. This should go quickly since I’ve read the first edition of the book and took a 3-day class on EJB 3.1 on the Sun Oracle campus in Broomfield, CO.

PCI DSS specification. These are the security requirements for any system that manages credit card data. I’ve touched on many of them previously. Again this should go quickly (cough) since I’ve already read the specification and I’ll just be refreshing my memory.

Introduction to Digital Certificates, Part 5: CA, RA and Repository

No Comments

This is part of a series on Digital Certificates.

I’ve casually mentioned a “Certificate Authority” earlier. What does this mean?

A “Certificate Authority” is the issuer in the “Public Key Infrastructure” (PKIX) system. It is actually composed of three components: a Registration Authority, a Certificate Authority and a Repository.

(Yes, one components has the same name as the whole. It’s usually not a problem since CA almost always refers to the whole.)

Casual implementations will put the three components in a single webapp but they have very different purposes and needs and should usually be split. At a minimum the architecture should reflect the internal structure.

Registration Authority

The Registration Authority is the component responsible for collecting information about the subject, vetting it, deciding on the appropriate policy and making the final approval. If successful it tells the Certificate Authority to produce the certificate.

Departmental and small enterprise CAs have an easy job – they can hook into the employee or student registrar systems. Is the person known? What is their job description? Done.

Issuing certs for servers is a little more complex but the RA can require any request for a server cert be vouched for by an employee’s cert.

Public CAs need to vet people and organizations from public information. A minimal cert for a person may link an email address to a certificate by requiring the user respond to an email sent to the provided address. This gives you reasonable confidence that this is the right person but you have no confidence that any name associated with the email address is valid.

A stronger requirement is that the person provides a credit card for a token payment. In essence you’re asking the credit card company to vouch for the person. If the charge goes through you have reasonable confidence that you have the correct name and address for the subject.

Really strong authentication requires the subject come into an office, in person, and provide strong ID like a driver’s license or passport.

The subject’s public key is provided by a self-signed cert or (Netscape and possibly other browsers) a signed ‘request’. The subject demonstrates he knows the public key by signing the cert or request.

Businesses can usually be authenticated by checking public business records. This can’t be easily automated and that’s why server certs cost more than individual certs. Open source projects are weird entities since they often don’t have business records and I don’t know if there’s a good way to authenticate them. You can’t just say “go to their mailing list” since anyone can create a rogue mailing list. Fortunately most open source projects have their own websites and it’s possible to put up a document containing a RA-provided value to prove that the subject controls that site. This is one way Google authenticates domain ownership, for example.

The registration authority is also responsible for requests to revoke a certificate from the subject. For instance the subject may realize that its server has been hacked and it can’t guarantee that its private key hasn’t been stolen.

How does the registration authority indicate approval to the certificate authority? One technique is for the RA to issue its own short-lived cert. The certificate authority can then copy all of the information to its own certificate before signing it and passing it on to the repository.

Vulnerabilities

Email accounts are hacked. Credit card information is stolen. Even business identity theft can happen. On a jury I would say there’s a preponderance of evidence but reasonable doubt short of measures like having the subject appear, in person, with a strong ID.

Another vulnerability is an attacker putting unauthorized approvals into the work queue for the certificate authority. This is a strong reason for keeping the certificate authority workqueue in a different database than the repository authority’s database. Fortunately this can be punted – either call the certificate authority directly (e.g., via a REST call) and let it handle it or hand the certificate off to a JMS server.

Finally the repository authority should maintain its own certificates in a secure location like a filesystem-based keystore. Never in the database itself.

Certificate Authority

The certificate authority is responsible for creating certificates based on the information provided by the registration authority. It can change the extensions based on policies associated with the signing certificate but it will generally create certificates based entirely on what the RA provided it.

There are four broad categories for how certs are signed.

  1. Low value certs can be signed by an entirely automated process using code similar to what I published in the last post.
  2. Medium value certs can require that an employee explicitly provide the decryption password for the issuing private key. This often requires the employee to be sitting at a console on the certificate authority.
  3. High value certs can require that multiple employees explicitly provide decryption passwords. One employee is not enough.
  4. Alternately high value certs can require the certification be signed by specialized hardware that never exports the private key. The higher the value the better the hardware. The details of how the hardware gets approval to issue the certificate vary by the hardware.

Certificate authorities are also responsible for signing Certificate Revocation Lists (CRLs) and (maybe) OCSP records. These are used to inform others that a certificate has been revoked.

The signed certs are passed on to the repository.

Vulnerabilities

The certificate authority should not be publicly available.

The certificate authority may get unauthorized approvals in the workqueue from the repository. It should consider performing its own sanity checks.

Again the certificate authority should maintain its own certificates in a secure location like a filesystem-based keystore. Never in the database itself.

Tighter security in the workqueue

With a little work it is possible to have much tighter security in the workqueue. Instead of the repository making blind submissions to the workqueue it can use a three-step process.

  1. Request a random nonce from the certificate authority
  2. Embed the nonce in the approval certificate, e.g., perhaps as a subject alternate name.
  3. Put the approval certificate in the workqueue.

The certificate workqueue has a corresponding process.

  1. Receive a nonce request from the registration authority. Create one and bind it to the subject DN in some way. (Use an HMAC digest of the subject DN as the nonce. This has the benefit of eliminating the need to maintain a local database.)
  2. Receive the approval certificate in the workqueue.
  3. Verify that the nonce is in the approval certificate.

This process seems burdensome but should be fairly easy to implement and will dramatically improve the security of the workqueue.

Repository

The repository is responsible for publishing the cert. It usually does this by some combination of HTTP, FTP and LDAP. It is also responsible for publishing Certificate Revocation Lists (CRLs) and OCSP.

Another option is providing a REST interface and an implementation of a java.security.cert.CertStore that maintains a local cache and will call the REST service for unfamiliar certs. This has the benefit of being standard and easily tested.

The repository is pretty dumb overall but the database behinds it needs to be very intelligent in order to prevent attackers from seeding it with unauthorized certificates.

Vulnerabilities

If the database behind the repository is compromised than the repository will feed unauthorized certs to users. One countermeasure is to maintain a list of approved root certificates in a local KeyStore and validate all certs before returning them to the caller. This will substantially improve the effort per request but that may be acceptable on lower volume repositories.

The repository may be subjected to denial-of-service attacks.

The repository may be subjected to unintentional denial-of-service attacks with naive use of OCSP requests by clients.

Database Security

Database security is crucial and in fact it appears to be a high risk factor for my efforts to deploy a prototype to the Google App Engine. (No final determination yet since Google is still working on a mysql-like database interface.)

The wrong way to do things is to extract the different attributes in a certificate and use normal object persistence to write the values to the database.

One right way to do things is to lock down the certificate table to revoke all INSERT, UPDATE and DELETE rights. Instead certificates use stored procedures:

INSERT: extract the cached values and use them to populate the appropriate columns.

UPDATE: certificates are never modified. Ancillary information, e.g., revocation date and reason, can be updated by a stored procedure.

DELETE: again certificates are never modified. A stored procedure can be used for soft deletions.

A variant is to use triggers that overwrite the cached fields on insertions and refuses to change those columns on updates. This has the benefit of allowing us to continue using ORM frameworks.

It’s important to remember that database backups and restorations sidestep stored procedures. You can lock the SQL but still have unauthorized certs put into the repository’s database by rogue administrators.

As always one database user should own the table and stored procedure and a different database user should own the data but have no rights to modify the table or stored procedures.

For More Information

RFC2459: Internet X.509 Public Key Infrastructure | Certificate and CRL Profile

RFC4387: Internet X.509 Public Key Infrastructure | Operational Protocols: Certificate Store Access via HTTP

RFC5019: The Lightweight Online Certificate Status Protocol (OCSP) Profile for High-Volume Environments

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

Introduction to Digital Certificates, Part 4: Creating Certs with Bouncy Castle

No Comments

This is part of a series on Digital Certificates.

Time for some code!

This implementation uses deprecated classes from Bouncy Castle. I’m still working on an implementation using the preferred classes.

The Unit Test

We’re good developers using Test-Driven Development so we start with the unit test. This is an abridged test that doesn’t attempt to verify policy issues.

  1. package com.otterca.repository.util;
  2.  
  3. import static org.testng.Assert.assertEquals;
  4. import static org.testng.Assert.fail;
  5.  
  6. import java.math.BigInteger;
  7. import java.security.KeyPair;
  8. import java.security.KeyPairGenerator;
  9. import java.security.Provider;
  10. import java.security.Security;
  11. import java.security.cert.X509Certificate;
  12. import java.util.Calendar;
  13. import java.util.List;
  14. import java.util.TimeZone;
  15.  
  16. import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
  17. import org.bouncycastle.asn1.x509.GeneralName;
  18. import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
  19. import org.bouncycastle.asn1.x509.X509Extension;
  20. import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
  21. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  22. import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
  23. import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
  24. import org.testng.annotations.Test;
  25.  
  26. /**
  27.  * Unit tests for X509CertificateBuilder.
  28.  *
  29.  * @author bgiles@otterca.com
  30.  */
  31. @SuppressWarnings("deprecation")
  32. public class X509CertificateBuilderTest {
  33.     private final Provider provider;
  34.     private final KeyPairGenerator keyPairGen;
  35.     private final BigInteger serial = BigInteger.ONE;
  36.     private final String subjectName = "DN=subject";
  37.     private final String issuerName = "DN=issuer";
  38.     private final Calendar notBefore;
  39.     private final Calendar notAfter;
  40.     private final KeyPair keyPair;
  41.  
  42.     public X509CertificateBuilderTest() throws Exception {
  43.         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
  44.  
  45.         provider = new BouncyCastleProvider();
  46.         Security.addProvider(provider);
  47.  
  48.         keyPairGen = KeyPairGenerator.getInstance("RSA", provider);
  49.         keyPairGen.initialize(512);
  50.         keyPair = keyPairGen.generateKeyPair();
  51.  
  52.         notBefore = Calendar.getInstance();
  53.         notBefore.set(Calendar.MILLISECOND, 0);
  54.         notAfter = Calendar.getInstance();
  55.         notAfter.setTime(notBefore.getTime());
  56.         notAfter.add(Calendar.YEAR, 1);
  57.     }
  58.  
  59.     /**
  60.      * Test builder when all properties are set.
  61.      *
  62.      * @throws Exception
  63.      */
  64.     @Test
  65.     public void testBuilderSelfSignedCert() throws Exception {
  66.         String email = "email";
  67.         String dnsName = "otterca.com";
  68.         String ipAddress = "127.0.0.1";
  69.         String dirName = "CN=subject";
  70.         String issuerEmail = "issuer email";
  71.         String issuerDnsName = "issuer.otterca.com";
  72.         String issuerIpAddress = "127.0.0.2";
  73.         String issuerDirName = "CN=issuer";
  74.  
  75.         // create certificate
  76.         X509CertificateBuilder builder = new X509CertificateBuilder();
  77.         builder.setSerialNumber(serial);
  78.         builder.setSubject(subjectName);
  79.         builder.setIssuer(subjectName);
  80.         builder.setNotBefore(notBefore.getTime());
  81.         builder.setNotAfter(notAfter.getTime());
  82.         builder.setPublicKey(keyPair.getPublic());
  83.         builder.setEmailAddresses(email);
  84.         builder.setDnsNames(dnsName);
  85.         builder.setIpAddresses(ipAddress);
  86.         builder.setDirectoryNames(dirName);
  87.         builder.setIssuerEmailAddresses(issuerEmail);
  88.         builder.setIssuerDnsNames(issuerDnsName);
  89.         builder.setIssuerIpAddresses(issuerIpAddress);
  90.         builder.setIssuerDirectoryNames(issuerDirName);
  91.  
  92.         X509Certificate cert = builder.build(keyPair.getPrivate());
  93.  
  94.         // perform basic validation.
  95.         cert.verify(keyPair.getPublic());
  96.  
  97.         // verify the mandatory attributes.
  98.         assertEquals(cert.getSerialNumber(), serial);
  99.         assertEquals(cert.getSubjectDN().getName(), subjectName);
  100.         assertEquals(cert.getIssuerDN().getName(), subjectName);
  101.         assertEquals(cert.getNotBefore(), notBefore.getTime());
  102.         assertEquals(cert.getNotAfter(), notAfter.getTime());
  103.         assertEquals(cert.getPublicKey(), keyPair.getPublic());
  104.  
  105.         JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
  106.  
  107.         // verify that we have a SKID.
  108.         SubjectKeyIdentifier kid = utils.createSubjectKeyIdentifier(keyPair.getPublic());
  109.         SubjectKeyIdentifier skid = new SubjectKeyIdentifierStructure(
  110.                 cert.getExtensionValue(X509Extension.subjectKeyIdentifier.toString()));
  111.         assertEquals(skid.getKeyIdentifier(), kid.getKeyIdentifier());
  112.  
  113.         // verify that we have an AKID.
  114.         AuthorityKeyIdentifier akid = new AuthorityKeyIdentifierStructure(
  115.                 cert.getExtensionValue(X509Extension.authorityKeyIdentifier.toString()));
  116.         assertEquals(akid.getKeyIdentifier(), kid.getKeyIdentifier());
  117.         assertEquals(akid.getAuthorityCertSerialNumber(), serial);
  118.         GeneralName[] akidNames = akid.getAuthorityCertIssuer().getNames();
  119.         assertEquals(akidNames.length, 1);
  120.         assertEquals(akidNames[0].getName().toString(), subjectName);
  121.  
  122.         // verify the subject alternative names.
  123.         for (List obj : cert.getSubjectAlternativeNames()) {
  124.             switch (((Number) obj.get(0)).intValue()) {
  125.             case GeneralName.rfc822Name:
  126.                 assertEquals(obj.get(1), email);
  127.                 break;
  128.             case GeneralName.dNSName:
  129.                 assertEquals(obj.get(1), dnsName);
  130.                 break;
  131.             case GeneralName.iPAddress:
  132.                 assertEquals(obj.get(1), ipAddress);
  133.                 break;
  134.             case GeneralName.directoryName:
  135.                 assertEquals(obj.get(1), dirName);
  136.                 break;
  137.             default:
  138.                 fail("unexpected subject alternative name");
  139.             }
  140.         }
  141.  
  142.         // verify the issuer alternative names.
  143.         for (List obj : cert.getIssuerAlternativeNames()) {
  144.             switch (((Number) obj.get(0)).intValue()) {
  145.             case GeneralName.rfc822Name:
  146.                 assertEquals(obj.get(1), issuerEmail);
  147.                 break;
  148.             case GeneralName.dNSName:
  149.                 assertEquals(obj.get(1), issuerDnsName);
  150.                 break;
  151.             case GeneralName.iPAddress:
  152.                 assertEquals(obj.get(1), issuerIpAddress);
  153.                 break;
  154.             case GeneralName.directoryName:
  155.                 assertEquals(obj.get(1), issuerDirName);
  156.                 break;
  157.             default:
  158.                 fail("unexpected issuer alternative name");
  159.             }
  160.         }
  161.  
  162.         // key usage
  163.         // basic constraint
  164.     }
  165.  
  166.     /**
  167.      * Test builder when missing serial number.
  168.      *
  169.      * @throws Exception
  170.      */
  171.     @Test(expectedExceptions = IllegalArgumentException.class)
  172.     public void testBuilderMissingSerialNumber() throws Exception {
  173.         X509CertificateBuilder builder = new X509CertificateBuilder();
  174.  
  175.         builder.setSubject(subjectName);
  176.         builder.setIssuer(issuerName);
  177.         builder.setNotBefore(notBefore.getTime());
  178.         builder.setNotAfter(notAfter.getTime());
  179.         builder.setPublicKey(keyPair.getPublic());
  180.         builder.build(keyPair.getPrivate());
  181.     }
  182.  
  183.     /**
  184.      * Test builder when missing subject.
  185.      *
  186.      * @throws Exception
  187.      */
  188.     @Test(expectedExceptions = IllegalArgumentException.class)
  189.     public void testBuilderMissingSubject() throws Exception {
  190.         X509CertificateBuilder builder = new X509CertificateBuilder();
  191.  
  192.         builder.setSerialNumber(serial);
  193.         builder.setIssuer(issuerName);
  194.         builder.setNotBefore(notBefore.getTime());
  195.         builder.setNotAfter(notAfter.getTime());
  196.         builder.setPublicKey(keyPair.getPublic());
  197.         builder.build(keyPair.getPrivate());
  198.     }
  199.  
  200.     /**
  201.      * Test builder when missing issuer.
  202.      *
  203.      * @throws Exception
  204.      */
  205.     @Test(expectedExceptions = IllegalArgumentException.class)
  206.     public void testBuilderMissingIssuer() throws Exception {
  207.         X509CertificateBuilder builder = new X509CertificateBuilder();
  208.  
  209.         builder.setSerialNumber(serial);
  210.         builder.setSubject(subjectName);
  211.         builder.setNotBefore(notBefore.getTime());
  212.         builder.setNotAfter(notAfter.getTime());
  213.         builder.setPublicKey(keyPair.getPublic());
  214.         builder.build(keyPair.getPrivate());
  215.     }
  216.  
  217.     /**
  218.      * Test builder when missing 'not before' date.
  219.      *
  220.      * @throws Exception
  221.      */
  222.     @Test(expectedExceptions = IllegalArgumentException.class)
  223.     public void testBuilderMissingNotBefore() throws Exception {
  224.         X509CertificateBuilder builder = new X509CertificateBuilder();
  225.  
  226.         builder.setSerialNumber(serial);
  227.         builder.setSubject(subjectName);
  228.         builder.setIssuer(issuerName);
  229.         builder.setNotAfter(notAfter.getTime());
  230.         builder.setPublicKey(keyPair.getPublic());
  231.         builder.build(keyPair.getPrivate());
  232.     }
  233.  
  234.     /**
  235.      * Test builder when missing 'not after' date.
  236.      *
  237.      * @throws Exception
  238.      */
  239.     @Test(expectedExceptions = IllegalArgumentException.class)
  240.     public void testBuilderMissingNotAfter() throws Exception {
  241.         X509CertificateBuilder builder = new X509CertificateBuilder();
  242.  
  243.         builder.setSerialNumber(serial);
  244.         builder.setSubject(subjectName);
  245.         builder.setIssuer(issuerName);
  246.         builder.setNotBefore(notBefore.getTime());
  247.         builder.setPublicKey(keyPair.getPublic());
  248.         builder.build(keyPair.getPrivate());
  249.     }
  250.  
  251.     /**
  252.      * Test builder when missing public key.
  253.      *
  254.      * @throws Exception
  255.      */
  256.     @Test(expectedExceptions = IllegalArgumentException.class)
  257.     public void testBuilderMissingPublicKey() throws Exception {
  258.         X509CertificateBuilder builder = new X509CertificateBuilder();
  259.  
  260.         builder.setSerialNumber(serial);
  261.         builder.setSubject(subjectName);
  262.         builder.setIssuer(issuerName);
  263.         builder.setNotBefore(notBefore.getTime());
  264.         builder.setNotAfter(notAfter.getTime());
  265.         builder.build(keyPair.getPrivate());
  266.     }
  267.  
  268.     /**
  269.      * Test builder when 'notBefore' date is after 'notAfter' date.
  270.      *
  271.      * @throws Exception
  272.      */
  273.     @Test(expectedExceptions = IllegalArgumentException.class)
  274.     public void testBuilderBadDates() throws Exception {
  275.         X509CertificateBuilder builder = new X509CertificateBuilder();
  276.  
  277.         builder.setSerialNumber(serial);
  278.         builder.setSubject(subjectName);
  279.         builder.setIssuer(issuerName);
  280.         builder.setNotBefore(notAfter.getTime());
  281.         builder.setNotAfter(notBefore.getTime());
  282.         builder.setPublicKey(keyPair.getPublic());
  283.         builder.build(keyPair.getPrivate());
  284.     }
  285. }
package com.otterca.repository.util;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;

import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
import org.testng.annotations.Test;

/**
 * Unit tests for X509CertificateBuilder.
 *
 * @author bgiles@otterca.com
 */
@SuppressWarnings("deprecation")
public class X509CertificateBuilderTest {
    private final Provider provider;
    private final KeyPairGenerator keyPairGen;
    private final BigInteger serial = BigInteger.ONE;
    private final String subjectName = "DN=subject";
    private final String issuerName = "DN=issuer";
    private final Calendar notBefore;
    private final Calendar notAfter;
    private final KeyPair keyPair;

    public X509CertificateBuilderTest() throws Exception {
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        keyPairGen = KeyPairGenerator.getInstance("RSA", provider);
        keyPairGen.initialize(512);
        keyPair = keyPairGen.generateKeyPair();

        notBefore = Calendar.getInstance();
        notBefore.set(Calendar.MILLISECOND, 0);
        notAfter = Calendar.getInstance();
        notAfter.setTime(notBefore.getTime());
        notAfter.add(Calendar.YEAR, 1);
    }

    /**
     * Test builder when all properties are set.
     *
     * @throws Exception
     */
    @Test
    public void testBuilderSelfSignedCert() throws Exception {
        String email = "email";
        String dnsName = "otterca.com";
        String ipAddress = "127.0.0.1";
        String dirName = "CN=subject";
        String issuerEmail = "issuer email";
        String issuerDnsName = "issuer.otterca.com";
        String issuerIpAddress = "127.0.0.2";
        String issuerDirName = "CN=issuer";

        // create certificate
        X509CertificateBuilder builder = new X509CertificateBuilder();
        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(subjectName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.setEmailAddresses(email);
        builder.setDnsNames(dnsName);
        builder.setIpAddresses(ipAddress);
        builder.setDirectoryNames(dirName);
        builder.setIssuerEmailAddresses(issuerEmail);
        builder.setIssuerDnsNames(issuerDnsName);
        builder.setIssuerIpAddresses(issuerIpAddress);
        builder.setIssuerDirectoryNames(issuerDirName);

        X509Certificate cert = builder.build(keyPair.getPrivate());

        // perform basic validation.
        cert.verify(keyPair.getPublic());

        // verify the mandatory attributes.
        assertEquals(cert.getSerialNumber(), serial);
        assertEquals(cert.getSubjectDN().getName(), subjectName);
        assertEquals(cert.getIssuerDN().getName(), subjectName);
        assertEquals(cert.getNotBefore(), notBefore.getTime());
        assertEquals(cert.getNotAfter(), notAfter.getTime());
        assertEquals(cert.getPublicKey(), keyPair.getPublic());

        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();

        // verify that we have a SKID.
        SubjectKeyIdentifier kid = utils.createSubjectKeyIdentifier(keyPair.getPublic());
        SubjectKeyIdentifier skid = new SubjectKeyIdentifierStructure(
                cert.getExtensionValue(X509Extension.subjectKeyIdentifier.toString()));
        assertEquals(skid.getKeyIdentifier(), kid.getKeyIdentifier());

        // verify that we have an AKID.
        AuthorityKeyIdentifier akid = new AuthorityKeyIdentifierStructure(
                cert.getExtensionValue(X509Extension.authorityKeyIdentifier.toString()));
        assertEquals(akid.getKeyIdentifier(), kid.getKeyIdentifier());
        assertEquals(akid.getAuthorityCertSerialNumber(), serial);
        GeneralName[] akidNames = akid.getAuthorityCertIssuer().getNames();
        assertEquals(akidNames.length, 1);
        assertEquals(akidNames[0].getName().toString(), subjectName);

        // verify the subject alternative names.
        for (List obj : cert.getSubjectAlternativeNames()) {
            switch (((Number) obj.get(0)).intValue()) {
            case GeneralName.rfc822Name:
                assertEquals(obj.get(1), email);
                break;
            case GeneralName.dNSName:
                assertEquals(obj.get(1), dnsName);
                break;
            case GeneralName.iPAddress:
                assertEquals(obj.get(1), ipAddress);
                break;
            case GeneralName.directoryName:
                assertEquals(obj.get(1), dirName);
                break;
            default:
                fail("unexpected subject alternative name");
            }
        }

        // verify the issuer alternative names.
        for (List obj : cert.getIssuerAlternativeNames()) {
            switch (((Number) obj.get(0)).intValue()) {
            case GeneralName.rfc822Name:
                assertEquals(obj.get(1), issuerEmail);
                break;
            case GeneralName.dNSName:
                assertEquals(obj.get(1), issuerDnsName);
                break;
            case GeneralName.iPAddress:
                assertEquals(obj.get(1), issuerIpAddress);
                break;
            case GeneralName.directoryName:
                assertEquals(obj.get(1), issuerDirName);
                break;
            default:
                fail("unexpected issuer alternative name");
            }
        }

        // key usage
        // basic constraint
    }

    /**
     * Test builder when missing serial number.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingSerialNumber() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing subject.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingSubject() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing issuer.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingIssuer() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing 'not before' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingNotBefore() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotAfter(notAfter.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing 'not after' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingNotAfter() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when missing public key.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderMissingPublicKey() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notBefore.getTime());
        builder.setNotAfter(notAfter.getTime());
        builder.build(keyPair.getPrivate());
    }

    /**
     * Test builder when 'notBefore' date is after 'notAfter' date.
     *
     * @throws Exception
     */
    @Test(expectedExceptions = IllegalArgumentException.class)
    public void testBuilderBadDates() throws Exception {
        X509CertificateBuilder builder = new X509CertificateBuilder();

        builder.setSerialNumber(serial);
        builder.setSubject(subjectName);
        builder.setIssuer(issuerName);
        builder.setNotBefore(notAfter.getTime());
        builder.setNotAfter(notBefore.getTime());
        builder.setPublicKey(keyPair.getPublic());
        builder.build(keyPair.getPrivate());
    }
}

The Code

The actual builder. As the javadocs point out the final version should use dependency injection for a few strategy methods.

  1. package com.otterca.repository.util;
  2.  
  3. import java.math.BigInteger;
  4. import java.security.InvalidKeyException;
  5. import java.security.KeyStore;
  6. import java.security.KeyStoreException;
  7. import java.security.NoSuchAlgorithmException;
  8. import java.security.NoSuchProviderException;
  9. import java.security.PrivateKey;
  10. import java.security.PublicKey;
  11. import java.security.SignatureException;
  12. import java.security.cert.CertificateEncodingException;
  13. import java.security.cert.CertificateException;
  14. import java.security.cert.CertificateExpiredException;
  15. import java.security.cert.CertificateNotYetValidException;
  16. import java.security.cert.CertificateParsingException;
  17. import java.security.cert.X509Certificate;
  18. import java.util.ArrayList;
  19. import java.util.Date;
  20. import java.util.List;
  21.  
  22. import javax.annotation.ParametersAreNonnullByDefault;
  23.  
  24. import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
  25. import org.bouncycastle.asn1.x509.BasicConstraints;
  26. import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
  27. import org.bouncycastle.asn1.x509.GeneralName;
  28. import org.bouncycastle.asn1.x509.GeneralNames;
  29. import org.bouncycastle.asn1.x509.KeyUsage;
  30. import org.bouncycastle.asn1.x509.X509Extensions;
  31. import org.bouncycastle.jce.X509Principal;
  32. import org.bouncycastle.x509.X509V3CertificateGenerator;
  33. import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
  34. import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
  35.  
  36. /**
  37.  * Convenience class that builds X509 certificates.
  38.  *
  39.  * @author bgiles@otterca.com
  40.  */
  41. @ParametersAreNonnullByDefault
  42. @SuppressWarnings("deprecation")
  43. public class X509CertificateBuilder {
  44.     public static final String SIGNATURE_ALGORITHM = "SHA256WITHRSA";
  45.     private KeyStore keyStore;
  46.     private X509Certificate issuer;
  47.     private BigInteger serial;
  48.     private X509Principal subjectDN;
  49.     private X509Principal issuerDN;
  50.     private Date notBefore;
  51.     private Date notAfter;
  52.     private PublicKey pubkey;
  53.     private final List<GeneralName> subjectNames = new ArrayList<>();
  54.     private final List<GeneralName> issuerNames = new ArrayList<>();
  55.     private KeyUsage keyUsage;
  56.     private ExtendedKeyUsage extendedKeyUsage;
  57.     private boolean basicConstraint;
  58.     private int pathLengthConstraint = -1;
  59.  
  60.     /**
  61.      * Default constructor that can be used to create self-signed certificates
  62.      * but not certificate chains.
  63.      *
  64.      * @param keyStore
  65.      */
  66.     public X509CertificateBuilder() {
  67.     }
  68.  
  69.     /**
  70.      * Constructor that requires keystore to validate certificate chain for
  71.      * issuer.
  72.      *
  73.      * @param keyStore
  74.      */
  75.     public X509CertificateBuilder(KeyStore keyStore) {
  76.         this.keyStore = keyStore;
  77.     }
  78.  
  79.     /**
  80.      * Reset builder to default state.
  81.      */
  82.     public void reset() {
  83.         issuer = null;
  84.         serial = null;
  85.         subjectDN = null;
  86.         issuerDN = null;
  87.         notBefore = null;
  88.         notAfter = null;
  89.         pubkey = null;
  90.         subjectNames.clear();
  91.         issuerNames.clear();
  92.         keyUsage = null;
  93.         basicConstraint = false;
  94.         pathLengthConstraint = -1;
  95.     }
  96.  
  97.     /**
  98.      * Set serial number.
  99.      *
  100.      * @param serial
  101.      * @return
  102.      */
  103.     public X509CertificateBuilder setSerialNumber(BigInteger serial) {
  104.         this.serial = serial;
  105.         return this;
  106.     }
  107.  
  108.     /**
  109.      * Set subject name (as X509Principal).
  110.      *
  111.      * @param dirName
  112.      * @return
  113.      */
  114.     public X509CertificateBuilder setSubject(String dirName) {
  115.         this.subjectDN = new X509Principal(dirName);
  116.         return this;
  117.     }
  118.  
  119.     /**
  120.      * Set issuer name (as X509Principal).
  121.      *
  122.      * @param dirName
  123.      * @return
  124.      */
  125.     public X509CertificateBuilder setIssuer(String dirName) {
  126.         this.issuerDN = new X509Principal(dirName);
  127.         return this;
  128.     }
  129.  
  130.     /**
  131.      * Set 'notBefore' date.
  132.      *
  133.      * @param notBefore
  134.      * @return
  135.      */
  136.     public X509CertificateBuilder setNotBefore(Date notBefore) {
  137.         this.notBefore = notBefore;
  138.         return this;
  139.     }
  140.  
  141.     /**
  142.      * Set 'notAfter' date.
  143.      *
  144.      * @param notAfter
  145.      * @return
  146.      */
  147.     public X509CertificateBuilder setNotAfter(Date notAfter) {
  148.         this.notAfter = notAfter;
  149.         return this;
  150.     }
  151.  
  152.     /**
  153.      * Set public key.
  154.      *
  155.      * @param pubkey
  156.      * @return
  157.      */
  158.     public X509CertificateBuilder setPublicKey(PublicKey pubkey) {
  159.         this.pubkey = pubkey;
  160.         return this;
  161.     }
  162.  
  163.     /**
  164.      * Set issuer's X509 certificate. This provides issuer's DN and alternate
  165.      * names and public key.
  166.      *
  167.      * @param issuer
  168.      * @param pubkey
  169.      * @return
  170.      */
  171.     public X509CertificateBuilder setIssuer(X509Certificate issuer) {
  172.         this.issuer = issuer;
  173.         return this;
  174.     }
  175.  
  176.     /**
  177.      * Set subject's email addresses (individual, server or CA).
  178.      *
  179.      * @param emailAddresses
  180.      * @return
  181.      */
  182.     public X509CertificateBuilder setEmailAddresses(String... emailAddresses) {
  183.         for (String address : emailAddresses) {
  184.             subjectNames.add(new GeneralName(GeneralName.rfc822Name, address));
  185.         }
  186.         return this;
  187.     }
  188.  
  189.     /**
  190.      * Set subject's DNS names (server or CA?). Note: servers should use their
  191.      * canonical hostname as the X.500 CommonName used as their subjectDN. (See
  192.      * above.) This provides more flexibility but many clients won't look for
  193.      * these extensions.
  194.      *
  195.      * @param dnsNames
  196.      * @return
  197.      */
  198.     public X509CertificateBuilder setDnsNames(String... dnsNames) {
  199.         for (String name : dnsNames) {
  200.             subjectNames.add(new GeneralName(GeneralName.dNSName, name));
  201.         }
  202.         return this;
  203.     }
  204.  
  205.     /**
  206.      * Set subject's IP Address (server).
  207.      *
  208.      * @param ipAddresses
  209.      * @return
  210.      */
  211.     public X509CertificateBuilder setIpAddresses(String... ipAddresses) {
  212.         for (String address : ipAddresses) {
  213.             subjectNames.add(new GeneralName(GeneralName.iPAddress, address));
  214.         }
  215.         return this;
  216.     }
  217.  
  218.     /**
  219.      * Set subject's directory names. I think this refers to alternate X.500
  220.      * principal names, not filesystem directories.
  221.      *
  222.      * @param dirNames
  223.      * @return
  224.      */
  225.     public X509CertificateBuilder setDirectoryNames(String... dirNames) {
  226.         for (String name : dirNames) {
  227.             subjectNames.add(new GeneralName(GeneralName.directoryName, name));
  228.         }
  229.         return this;
  230.     }
  231.  
  232.     /**
  233.      * Set issuer's email addresses (individual, server or CA).
  234.      *
  235.      * @param emailAddresses
  236.      * @return
  237.      */
  238.     public X509CertificateBuilder setIssuerEmailAddresses(String... emailAddresses) {
  239.         for (String address : emailAddresses) {
  240.             issuerNames.add(new GeneralName(GeneralName.rfc822Name, address));
  241.         }
  242.         return this;
  243.     }
  244.  
  245.     /**
  246.      * Set issuer's DNS names (server or CA?). Note: servers should use their
  247.      * canonical hostname as the X.500 CommonName used as their subjectDN. (See
  248.      * above.) This provides more flexibility but many clients won't look for
  249.      * these extensions.
  250.      *
  251.      * @param dnsNames
  252.      * @return
  253.      */
  254.     public X509CertificateBuilder setIssuerDnsNames(String... dnsNames) {
  255.         for (String name : dnsNames) {
  256.             issuerNames.add(new GeneralName(GeneralName.dNSName, name));
  257.         }
  258.         return this;
  259.     }
  260.  
  261.     /**
  262.      * Set issuer's IP Address (server).
  263.      *
  264.      * @param ipAddresses
  265.      * @return
  266.      */
  267.     public X509CertificateBuilder setIssuerIpAddresses(String... ipAddresses) {
  268.         for (String address : ipAddresses) {
  269.             issuerNames.add(new GeneralName(GeneralName.iPAddress, address));
  270.         }
  271.         return this;
  272.     }
  273.  
  274.     /**
  275.      * Set issuer's directory names. I think this refers to alternate X.500
  276.      * principal names, not filesystem directories.
  277.      *
  278.      * @param dirNames
  279.      * @return
  280.      */
  281.     public X509CertificateBuilder setIssuerDirectoryNames(String... dirNames) {
  282.         for (String name : dirNames) {
  283.             issuerNames.add(new GeneralName(GeneralName.directoryName, name));
  284.         }
  285.         return this;
  286.     }
  287.  
  288.     /**
  289.      * Set key usage. TODO: provide parameters that don't expose implementation
  290.      * details.
  291.      *
  292.      * @param usage
  293.      * @return
  294.      */
  295.     public X509CertificateBuilder setKeyUsage(KeyUsage usage) {
  296.         this.keyUsage = usage;
  297.         return this;
  298.     }
  299.  
  300.     /**
  301.      * Set extended key usage. TODO: provide parameters that don't expose
  302.      * implementation details.
  303.      *
  304.      * @param usage
  305.      * @return
  306.      */
  307.     public X509CertificateBuilder setExtendedKeyUsage(ExtendedKeyUsage extendedKeyUsage) {
  308.         this.extendedKeyUsage = extendedKeyUsage;
  309.         return this;
  310.     }
  311.  
  312.     /**
  313.      * Set the certificate's Basic Constraint (can this certificate be used to
  314.      * sign other certificates?). There are no restrictions on certification
  315.      * path length.
  316.      *
  317.      * @param basicConstraint
  318.      * @return
  319.      */
  320.     public X509CertificateBuilder setBasicConstraints(boolean basicConstraint) {
  321.         this.basicConstraint = basicConstraint;
  322.         return this;
  323.     }
  324.  
  325.     /**
  326.      * Set the certificate's Basic Constraint (can this certificate be used to
  327.      * sign other certificates?) and maximum certification path length. A value
  328.      * of 0 means that this certificate can be used to sign leafs but cannot be
  329.      * used to create other signing certs.
  330.      *
  331.      * @param basicConstraint
  332.      * @param pathLengthConstraint
  333.      * @return
  334.      */
  335.     public X509CertificateBuilder setBasicConstraints(boolean basicConstraint,
  336.             int pathLengthConstraint) {
  337.         this.basicConstraint = basicConstraint;
  338.         this.pathLengthConstraint = pathLengthConstraint;
  339.         return this;
  340.     }
  341.  
  342.     /**
  343.      * Perform any other validation checks on issuer. This method can be
  344.      * replaced by an injected strategy.
  345.      *
  346.      * @param ex
  347.      */
  348.     public void checkIssuer(X509CertificateBuilderIllegalArgumentException ex)
  349.             throws CertificateParsingException, KeyStoreException {
  350.         if (issuer == null) {
  351.             return;
  352.         }
  353.         if (keyStore == null) {
  354.             ex.setIssuerCannotSignCertificates(true);
  355.             return;
  356.         }
  357.  
  358.         // look up issuer's certificate chain in our keystore using
  359.         // the issuer's subject DN as the alias.
  360.         String alias = issuer.getSubjectDN().getName();
  361.         if (!keyStore.containsAlias(alias)) {
  362.             ex.setUnknownIssuer(true);
  363.             return;
  364.         }
  365.  
  366.         // validate the certificate chain.
  367.         X509Certificate last = null;
  368.         X509Certificate[] chain = (X509Certificate[]) keyStore.getCertificateChain(alias);
  369.         for (X509Certificate cert : chain) {
  370.             if (cert.getBasicConstraints() < 0) {
  371.                 ex.setIssuerCannotSignCertificates(true);
  372.             }
  373.             try {
  374.                 cert.checkValidity();
  375.             } catch (CertificateExpiredException e) {
  376.                 ex.setIssuerCannotSignCertificates(true);
  377.             } catch (CertificateNotYetValidException e) {
  378.                 ex.setIssuerCannotSignCertificates(true);
  379.             }
  380.             if (last != null) {
  381.                 try {
  382.                     last.verify(cert.getPublicKey());
  383.                 } catch (CertificateException e) {
  384.                     ex.setIssuerCannotSignCertificates(true);
  385.                 } catch (SignatureException e) {
  386.                     ex.setIssuerCannotSignCertificates(true);
  387.                 } catch (InvalidKeyException e) {
  388.                     ex.setIssuerCannotSignCertificates(true);
  389.                 } catch (NoSuchProviderException e) {
  390.                     ex.setIssuerCannotSignCertificates(true);
  391.                 } catch (NoSuchAlgorithmException e) {
  392.                     ex.setIssuerCannotSignCertificates(true);
  393.                 }
  394.             }
  395.             last = cert;
  396.         }
  397.  
  398.         // at this point we have a valid certificate chain and
  399.         // have to trust that the keystore only contains trusted
  400.         // certificates.
  401.     }
  402.  
  403.     /**
  404.      * Perform any other policy check. This method can be replaced by an
  405.      * injected strategy.
  406.      *
  407.      * @param ex
  408.      */
  409.     public void checkPolicy(X509CertificateBuilderIllegalArgumentException ex) {
  410.  
  411.     }
  412.  
  413.     /**
  414.      * Establish any other policy. For instance this might add (or remove)
  415.      * alternative names, add or remove key usage constraints, etc. This method
  416.      * can be replaced by an injected strategy.
  417.      *
  418.      * @param ex
  419.      */
  420.     public void establishPolicy() {
  421.         // ensure keyCertSign is (only) available on signing keys.
  422.         if (basicConstraint) {
  423.             if (keyUsage == null) {
  424.                 keyUsage = new KeyUsage(KeyUsage.keyCertSign);
  425.             } else {
  426.                 keyUsage = new KeyUsage(keyUsage.intValue() | KeyUsage.keyCertSign);
  427.             }
  428.         } else {
  429.             if (keyUsage != null) {
  430.                 keyUsage = new KeyUsage(keyUsage.intValue()
  431.                         & (Integer.MAX_VALUE ^ KeyUsage.keyCertSign));
  432.             }
  433.         }
  434.  
  435.         // other policies..
  436.     }
  437.  
  438.     /**
  439.      * Build the X509 certificate.
  440.      *
  441.      * Implementation note: this uses the deprecated methods until I can find
  442.      * documentation on using the newer classes.
  443.      *
  444.      * @param pkey
  445.      * @return
  446.      * @throws InvalidKeyException
  447.      * @throws NoSuchAlgorithmException
  448.      * @throws SignatureException
  449.      * @throws CertificateEncodingException
  450.      * @throws CertificateParsingException
  451.      */
  452.     public X509Certificate build(PrivateKey pkey) throws InvalidKeyException,
  453.             NoSuchAlgorithmException, SignatureException, CertificateEncodingException,
  454.             CertificateParsingException, KeyStoreException {
  455.  
  456.         // verify we have everything we need....
  457.         X509CertificateBuilderIllegalArgumentException ex = new X509CertificateBuilderIllegalArgumentException();
  458.  
  459.         if (issuer == null) {
  460.             // require issuer cert for everything other than self-signed certs.
  461.             if ((issuerDN != null) && (issuerDN.equals(subjectDN))) {
  462.                 ex.setMissingIssuerCertificate(true);
  463.             }
  464.         } else {
  465.             // verify issuer&#039;s cert is active.
  466.             Date now = new Date();
  467.             if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
  468.                 ex.setInvalidIssuer(true);
  469.             }
  470.  
  471.             // verify our &#039;notBefore&#039; is within range of issuer&#039;s certificate.
  472.             if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
  473.                 setNotBefore(issuer.getNotBefore());
  474.             }
  475.             if (notBefore.after(issuer.getNotAfter())) {
  476.                 ex.setUnacceptableDateRange(true);
  477.             }
  478.  
  479.             // verify our &#039;notAfter&#039; is within range of issuer&#039;s certificate.
  480.             if ((notAfter == null) || notAfter.after(issuer.getNotAfter())) {
  481.                 setNotAfter(issuer.getNotAfter());
  482.             }
  483.             if (notAfter.before(issuer.getNotBefore())) {
  484.                 ex.setUnacceptableDateRange(true);
  485.             }
  486.  
  487.             // verify issuer can sign certificates
  488.             int pathLenConstraint = issuer.getBasicConstraints();
  489.             if (pathLenConstraint  0) {
  490.             throw ex;
  491.         }
  492.  
  493.         X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
  494.  
  495.         // set the mandatory properties
  496.         generator.setSerialNumber(serial);
  497.         generator.setIssuerDN(issuerDN);
  498.         generator.setSubjectDN(subjectDN);
  499.         generator.setNotBefore(notBefore);
  500.         generator.setNotAfter(notAfter);
  501.         generator.setPublicKey(pubkey);
  502.         generator.setSignatureAlgorithm(SIGNATURE_ALGORITHM);
  503.  
  504.         // can this certificate be used to sign more certificates?
  505.         // make sure pathlenthconstraint is always lower than issuer's.
  506.         if (basicConstraint) {
  507.             if ((issuer != null) && (pathLengthConstraint > issuer.getBasicConstraints() - 1)) {
  508.                 pathLengthConstraint = issuer.getBasicConstraints() - 1;
  509.             }
  510.             if (pathLengthConstraint >= 0) {
  511.                 generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  512.                         pathLengthConstraint));
  513.             } else {
  514.                 generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  515.                         true));
  516.             }
  517.         } else {
  518.             generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
  519.                     false));
  520.         }
  521.  
  522.         // set the X509 optional stuff.
  523.  
  524.         // we always add the subject key identifier.
  525.         SubjectKeyIdentifierStructure skis = new SubjectKeyIdentifierStructure(pubkey);
  526.         generator.addExtension(X509Extensions.SubjectKeyIdentifier, false, skis);
  527.  
  528.         // we always add the authority key identifier if possible.
  529.         if (issuer != null) {
  530.             // signed certificates....
  531.             AuthorityKeyIdentifierStructure akis = new AuthorityKeyIdentifierStructure(issuer);
  532.             generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  533.         } else if (subjectDN.equals(issuerDN)) {
  534.             // self-signed certificates....
  535.             GeneralNames issuerName = new GeneralNames(new GeneralName(GeneralName.directoryName,
  536.                     issuerDN));
  537.             AuthorityKeyIdentifier akis = new AuthorityKeyIdentifierStructure(pubkey);
  538.             akis = new AuthorityKeyIdentifier(akis.getKeyIdentifier(), issuerName, serial);
  539.             generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  540.         }
  541.  
  542.         // establish any other policy.
  543.         establishPolicy();
  544.  
  545.         // add subject alternative names if available - this is email, dns
  546.         // names, etc.
  547.         if (!subjectNames.isEmpty()) {
  548.             generator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
  549.                     subjectNames.toArray(new GeneralName[0])));
  550.         }
  551.  
  552.         // add issuer alternative names if available - this is email, dns
  553.         // names, etc.
  554.         if (!issuerNames.isEmpty()) {
  555.             generator.addExtension(X509Extensions.IssuerAlternativeName, false, new GeneralNames(
  556.                     issuerNames.toArray(new GeneralName[0])));
  557.         }
  558.  
  559.         // add mandatory key usage constraints.
  560.         if (keyUsage != null) {
  561.             generator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
  562.         }
  563.  
  564.         // add optional extended key usage constraints.
  565.         if (extendedKeyUsage != null) {
  566.             generator.addExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage);
  567.         }
  568.  
  569.         X509Certificate cert = generator.generate(pkey);
  570.         return cert;
  571.     }
  572. }
package com.otterca.repository.util;

import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.ParametersAreNonnullByDefault;

import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

/**
 * Convenience class that builds X509 certificates.
 *
 * @author bgiles@otterca.com
 */
@ParametersAreNonnullByDefault
@SuppressWarnings("deprecation")
public class X509CertificateBuilder {
    public static final String SIGNATURE_ALGORITHM = "SHA256WITHRSA";
    private KeyStore keyStore;
    private X509Certificate issuer;
    private BigInteger serial;
    private X509Principal subjectDN;
    private X509Principal issuerDN;
    private Date notBefore;
    private Date notAfter;
    private PublicKey pubkey;
    private final List<GeneralName> subjectNames = new ArrayList<>();
    private final List<GeneralName> issuerNames = new ArrayList<>();
    private KeyUsage keyUsage;
    private ExtendedKeyUsage extendedKeyUsage;
    private boolean basicConstraint;
    private int pathLengthConstraint = -1;

    /**
     * Default constructor that can be used to create self-signed certificates
     * but not certificate chains.
     *
     * @param keyStore
     */
    public X509CertificateBuilder() {
    }

    /**
     * Constructor that requires keystore to validate certificate chain for
     * issuer.
     *
     * @param keyStore
     */
    public X509CertificateBuilder(KeyStore keyStore) {
        this.keyStore = keyStore;
    }

    /**
     * Reset builder to default state.
     */
    public void reset() {
        issuer = null;
        serial = null;
        subjectDN = null;
        issuerDN = null;
        notBefore = null;
        notAfter = null;
        pubkey = null;
        subjectNames.clear();
        issuerNames.clear();
        keyUsage = null;
        basicConstraint = false;
        pathLengthConstraint = -1;
    }

    /**
     * Set serial number.
     *
     * @param serial
     * @return
     */
    public X509CertificateBuilder setSerialNumber(BigInteger serial) {
        this.serial = serial;
        return this;
    }

    /**
     * Set subject name (as X509Principal).
     *
     * @param dirName
     * @return
     */
    public X509CertificateBuilder setSubject(String dirName) {
        this.subjectDN = new X509Principal(dirName);
        return this;
    }

    /**
     * Set issuer name (as X509Principal).
     *
     * @param dirName
     * @return
     */
    public X509CertificateBuilder setIssuer(String dirName) {
        this.issuerDN = new X509Principal(dirName);
        return this;
    }

    /**
     * Set 'notBefore' date.
     *
     * @param notBefore
     * @return
     */
    public X509CertificateBuilder setNotBefore(Date notBefore) {
        this.notBefore = notBefore;
        return this;
    }

    /**
     * Set 'notAfter' date.
     *
     * @param notAfter
     * @return
     */
    public X509CertificateBuilder setNotAfter(Date notAfter) {
        this.notAfter = notAfter;
        return this;
    }

    /**
     * Set public key.
     *
     * @param pubkey
     * @return
     */
    public X509CertificateBuilder setPublicKey(PublicKey pubkey) {
        this.pubkey = pubkey;
        return this;
    }

    /**
     * Set issuer's X509 certificate. This provides issuer's DN and alternate
     * names and public key.
     *
     * @param issuer
     * @param pubkey
     * @return
     */
    public X509CertificateBuilder setIssuer(X509Certificate issuer) {
        this.issuer = issuer;
        return this;
    }

    /**
     * Set subject's email addresses (individual, server or CA).
     *
     * @param emailAddresses
     * @return
     */
    public X509CertificateBuilder setEmailAddresses(String... emailAddresses) {
        for (String address : emailAddresses) {
            subjectNames.add(new GeneralName(GeneralName.rfc822Name, address));
        }
        return this;
    }

    /**
     * Set subject's DNS names (server or CA?). Note: servers should use their
     * canonical hostname as the X.500 CommonName used as their subjectDN. (See
     * above.) This provides more flexibility but many clients won't look for
     * these extensions.
     *
     * @param dnsNames
     * @return
     */
    public X509CertificateBuilder setDnsNames(String... dnsNames) {
        for (String name : dnsNames) {
            subjectNames.add(new GeneralName(GeneralName.dNSName, name));
        }
        return this;
    }

    /**
     * Set subject's IP Address (server).
     *
     * @param ipAddresses
     * @return
     */
    public X509CertificateBuilder setIpAddresses(String... ipAddresses) {
        for (String address : ipAddresses) {
            subjectNames.add(new GeneralName(GeneralName.iPAddress, address));
        }
        return this;
    }

    /**
     * Set subject's directory names. I think this refers to alternate X.500
     * principal names, not filesystem directories.
     *
     * @param dirNames
     * @return
     */
    public X509CertificateBuilder setDirectoryNames(String... dirNames) {
        for (String name : dirNames) {
            subjectNames.add(new GeneralName(GeneralName.directoryName, name));
        }
        return this;
    }

    /**
     * Set issuer's email addresses (individual, server or CA).
     *
     * @param emailAddresses
     * @return
     */
    public X509CertificateBuilder setIssuerEmailAddresses(String... emailAddresses) {
        for (String address : emailAddresses) {
            issuerNames.add(new GeneralName(GeneralName.rfc822Name, address));
        }
        return this;
    }

    /**
     * Set issuer's DNS names (server or CA?). Note: servers should use their
     * canonical hostname as the X.500 CommonName used as their subjectDN. (See
     * above.) This provides more flexibility but many clients won't look for
     * these extensions.
     *
     * @param dnsNames
     * @return
     */
    public X509CertificateBuilder setIssuerDnsNames(String... dnsNames) {
        for (String name : dnsNames) {
            issuerNames.add(new GeneralName(GeneralName.dNSName, name));
        }
        return this;
    }

    /**
     * Set issuer's IP Address (server).
     *
     * @param ipAddresses
     * @return
     */
    public X509CertificateBuilder setIssuerIpAddresses(String... ipAddresses) {
        for (String address : ipAddresses) {
            issuerNames.add(new GeneralName(GeneralName.iPAddress, address));
        }
        return this;
    }

    /**
     * Set issuer's directory names. I think this refers to alternate X.500
     * principal names, not filesystem directories.
     *
     * @param dirNames
     * @return
     */
    public X509CertificateBuilder setIssuerDirectoryNames(String... dirNames) {
        for (String name : dirNames) {
            issuerNames.add(new GeneralName(GeneralName.directoryName, name));
        }
        return this;
    }

    /**
     * Set key usage. TODO: provide parameters that don't expose implementation
     * details.
     *
     * @param usage
     * @return
     */
    public X509CertificateBuilder setKeyUsage(KeyUsage usage) {
        this.keyUsage = usage;
        return this;
    }

    /**
     * Set extended key usage. TODO: provide parameters that don't expose
     * implementation details.
     *
     * @param usage
     * @return
     */
    public X509CertificateBuilder setExtendedKeyUsage(ExtendedKeyUsage extendedKeyUsage) {
        this.extendedKeyUsage = extendedKeyUsage;
        return this;
    }

    /**
     * Set the certificate's Basic Constraint (can this certificate be used to
     * sign other certificates?). There are no restrictions on certification
     * path length.
     *
     * @param basicConstraint
     * @return
     */
    public X509CertificateBuilder setBasicConstraints(boolean basicConstraint) {
        this.basicConstraint = basicConstraint;
        return this;
    }

    /**
     * Set the certificate's Basic Constraint (can this certificate be used to
     * sign other certificates?) and maximum certification path length. A value
     * of 0 means that this certificate can be used to sign leafs but cannot be
     * used to create other signing certs.
     *
     * @param basicConstraint
     * @param pathLengthConstraint
     * @return
     */
    public X509CertificateBuilder setBasicConstraints(boolean basicConstraint,
            int pathLengthConstraint) {
        this.basicConstraint = basicConstraint;
        this.pathLengthConstraint = pathLengthConstraint;
        return this;
    }

    /**
     * Perform any other validation checks on issuer. This method can be
     * replaced by an injected strategy.
     *
     * @param ex
     */
    public void checkIssuer(X509CertificateBuilderIllegalArgumentException ex)
            throws CertificateParsingException, KeyStoreException {
        if (issuer == null) {
            return;
        }
        if (keyStore == null) {
            ex.setIssuerCannotSignCertificates(true);
            return;
        }

        // look up issuer's certificate chain in our keystore using
        // the issuer's subject DN as the alias.
        String alias = issuer.getSubjectDN().getName();
        if (!keyStore.containsAlias(alias)) {
            ex.setUnknownIssuer(true);
            return;
        }

        // validate the certificate chain.
        X509Certificate last = null;
        X509Certificate[] chain = (X509Certificate[]) keyStore.getCertificateChain(alias);
        for (X509Certificate cert : chain) {
            if (cert.getBasicConstraints() < 0) {
                ex.setIssuerCannotSignCertificates(true);
            }
            try {
                cert.checkValidity();
            } catch (CertificateExpiredException e) {
                ex.setIssuerCannotSignCertificates(true);
            } catch (CertificateNotYetValidException e) {
                ex.setIssuerCannotSignCertificates(true);
            }
            if (last != null) {
                try {
                    last.verify(cert.getPublicKey());
                } catch (CertificateException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (SignatureException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (InvalidKeyException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (NoSuchProviderException e) {
                    ex.setIssuerCannotSignCertificates(true);
                } catch (NoSuchAlgorithmException e) {
                    ex.setIssuerCannotSignCertificates(true);
                }
            }
            last = cert;
        }

        // at this point we have a valid certificate chain and
        // have to trust that the keystore only contains trusted
        // certificates.
    }

    /**
     * Perform any other policy check. This method can be replaced by an
     * injected strategy.
     *
     * @param ex
     */
    public void checkPolicy(X509CertificateBuilderIllegalArgumentException ex) {

    }

    /**
     * Establish any other policy. For instance this might add (or remove)
     * alternative names, add or remove key usage constraints, etc. This method
     * can be replaced by an injected strategy.
     *
     * @param ex
     */
    public void establishPolicy() {
        // ensure keyCertSign is (only) available on signing keys.
        if (basicConstraint) {
            if (keyUsage == null) {
                keyUsage = new KeyUsage(KeyUsage.keyCertSign);
            } else {
                keyUsage = new KeyUsage(keyUsage.intValue() | KeyUsage.keyCertSign);
            }
        } else {
            if (keyUsage != null) {
                keyUsage = new KeyUsage(keyUsage.intValue()
                        & (Integer.MAX_VALUE ^ KeyUsage.keyCertSign));
            }
        }

        // other policies..
    }

    /**
     * Build the X509 certificate.
     *
     * Implementation note: this uses the deprecated methods until I can find
     * documentation on using the newer classes.
     *
     * @param pkey
     * @return
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws CertificateEncodingException
     * @throws CertificateParsingException
     */
    public X509Certificate build(PrivateKey pkey) throws InvalidKeyException,
            NoSuchAlgorithmException, SignatureException, CertificateEncodingException,
            CertificateParsingException, KeyStoreException {

        // verify we have everything we need....
        X509CertificateBuilderIllegalArgumentException ex = new X509CertificateBuilderIllegalArgumentException();

        if (issuer == null) {
            // require issuer cert for everything other than self-signed certs.
            if ((issuerDN != null) && (issuerDN.equals(subjectDN))) {
                ex.setMissingIssuerCertificate(true);
            }
        } else {
            // verify issuer&#039;s cert is active.
            Date now = new Date();
            if (!(issuer.getNotBefore().before(now) && now.before(issuer.getNotAfter()))) {
                ex.setInvalidIssuer(true);
            }

            // verify our &#039;notBefore&#039; is within range of issuer&#039;s certificate.
            if ((notBefore == null) || notBefore.before(issuer.getNotBefore())) {
                setNotBefore(issuer.getNotBefore());
            }
            if (notBefore.after(issuer.getNotAfter())) {
                ex.setUnacceptableDateRange(true);
            }

            // verify our &#039;notAfter&#039; is within range of issuer&#039;s certificate.
            if ((notAfter == null) || notAfter.after(issuer.getNotAfter())) {
                setNotAfter(issuer.getNotAfter());
            }
            if (notAfter.before(issuer.getNotBefore())) {
                ex.setUnacceptableDateRange(true);
            }

            // verify issuer can sign certificates
            int pathLenConstraint = issuer.getBasicConstraints();
            if (pathLenConstraint  0) {
            throw ex;
        }

        X509V3CertificateGenerator generator = new X509V3CertificateGenerator();

        // set the mandatory properties
        generator.setSerialNumber(serial);
        generator.setIssuerDN(issuerDN);
        generator.setSubjectDN(subjectDN);
        generator.setNotBefore(notBefore);
        generator.setNotAfter(notAfter);
        generator.setPublicKey(pubkey);
        generator.setSignatureAlgorithm(SIGNATURE_ALGORITHM);

        // can this certificate be used to sign more certificates?
        // make sure pathlenthconstraint is always lower than issuer's.
        if (basicConstraint) {
            if ((issuer != null) && (pathLengthConstraint > issuer.getBasicConstraints() - 1)) {
                pathLengthConstraint = issuer.getBasicConstraints() - 1;
            }
            if (pathLengthConstraint >= 0) {
                generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                        pathLengthConstraint));
            } else {
                generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                        true));
            }
        } else {
            generator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(
                    false));
        }

        // set the X509 optional stuff.

        // we always add the subject key identifier.
        SubjectKeyIdentifierStructure skis = new SubjectKeyIdentifierStructure(pubkey);
        generator.addExtension(X509Extensions.SubjectKeyIdentifier, false, skis);

        // we always add the authority key identifier if possible.
        if (issuer != null) {
            // signed certificates....
            AuthorityKeyIdentifierStructure akis = new AuthorityKeyIdentifierStructure(issuer);
            generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
        } else if (subjectDN.equals(issuerDN)) {
            // self-signed certificates....
            GeneralNames issuerName = new GeneralNames(new GeneralName(GeneralName.directoryName,
                    issuerDN));
            AuthorityKeyIdentifier akis = new AuthorityKeyIdentifierStructure(pubkey);
            akis = new AuthorityKeyIdentifier(akis.getKeyIdentifier(), issuerName, serial);
            generator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
        }

        // establish any other policy.
        establishPolicy();

        // add subject alternative names if available - this is email, dns
        // names, etc.
        if (!subjectNames.isEmpty()) {
            generator.addExtension(X509Extensions.SubjectAlternativeName, false, new GeneralNames(
                    subjectNames.toArray(new GeneralName[0])));
        }

        // add issuer alternative names if available - this is email, dns
        // names, etc.
        if (!issuerNames.isEmpty()) {
            generator.addExtension(X509Extensions.IssuerAlternativeName, false, new GeneralNames(
                    issuerNames.toArray(new GeneralName[0])));
        }

        // add mandatory key usage constraints.
        if (keyUsage != null) {
            generator.addExtension(X509Extensions.KeyUsage, true, keyUsage);
        }

        // add optional extended key usage constraints.
        if (extendedKeyUsage != null) {
            generator.addExtension(X509Extensions.ExtendedKeyUsage, false, extendedKeyUsage);
        }

        X509Certificate cert = generator.generate(pkey);
        return cert;
    }
}

The Exception

The builder throws a specialized IllegalArgumentException if it is missing required values or there’s a problem with them. This exception allows multiple errors to be identified at one time.

The final version will have a better ‘message’ that lists all of the errors.

  1. package com.otterca.repository.util;
  2.  
  3. /**
  4.  * IllegalArgumentException that bundles multiple errors into a single
  5.  * exception.
  6.  *
  7.  * @author bgiles@otterca.com
  8.  */
  9. public class X509CertificateBuilderIllegalArgumentException extends IllegalArgumentException {
  10.     private static final long serialVersionUID = 1L;
  11.     private boolean missingIssuerCertificate;
  12.     private boolean missingSerialNumber;
  13.     private boolean missingSubject;
  14.     private boolean missingIssuer;
  15.     private boolean missingNotBeforeDate;
  16.     private boolean missingNotAfterDate;
  17.     private boolean missingPublicKey;
  18.     private boolean badDateRange;
  19.     private boolean invalidIssuer;
  20.     private boolean issuerCannotSignCertificates;
  21.     private boolean unacceptableDateRange;
  22.     private boolean missingKeyUsage;
  23.     private boolean unknownIssuer;
  24.     private int errorCount = 0;
  25.  
  26.     /**
  27.      * @return the number of errors.
  28.      */
  29.     public int getErrorCount() {
  30.         return errorCount;
  31.     }
  32.  
  33.     /**
  34.      * @return the missingIssuerCertificate
  35.      */
  36.     public boolean isMissingIssuerCertificate() {
  37.         return missingIssuerCertificate;
  38.     }
  39.  
  40.     /**
  41.      * @return the missingSerialNumber
  42.      */
  43.     public boolean isMissingSerialNumber() {
  44.         return missingSerialNumber;
  45.     }
  46.  
  47.     /**
  48.      * @return the missingSubject
  49.      */
  50.     public boolean isMissingSubject() {
  51.         return missingSubject;
  52.     }
  53.  
  54.     /**
  55.      * @return the missingIssuer
  56.      */
  57.     public boolean isMissingIssuer() {
  58.         return missingIssuer;
  59.     }
  60.  
  61.     /**
  62.      * @return the missingNotBeforeDate
  63.      */
  64.     public boolean isMissingNotBeforeDate() {
  65.         return missingNotBeforeDate;
  66.     }
  67.  
  68.     /**
  69.      * @return the missingNotAfterDate
  70.      */
  71.     public boolean isMissingNotAfterDate() {
  72.         return missingNotAfterDate;
  73.     }
  74.  
  75.     /**
  76.      * @return the missingPublicKey
  77.      */
  78.     public boolean isMissingPublicKey() {
  79.         return missingPublicKey;
  80.     }
  81.  
  82.     /**
  83.      * @return the badDateRange
  84.      */
  85.     public boolean isBadDateRange() {
  86.         return badDateRange;
  87.     }
  88.  
  89.     /**
  90.      * @return the invalidIssuer
  91.      */
  92.     public boolean isInvalidIssuer() {
  93.         return invalidIssuer;
  94.     }
  95.  
  96.     /**
  97.      * @return the issuerCannotSignCertificates
  98.      */
  99.     public boolean isIssuerCannotSignCertificates() {
  100.         return issuerCannotSignCertificates;
  101.     }
  102.  
  103.     /**
  104.      * @return the unknownIssuer
  105.      */
  106.     boolean isUnknownIssuer() {
  107.         return unknownIssuer;
  108.     }
  109.  
  110.     /**
  111.      * @return the unacceptableDateRange
  112.      */
  113.     boolean isUnacceptableDateRange() {
  114.         return unacceptableDateRange;
  115.     }
  116.  
  117.     /**
  118.      * @return the missingKeyUsage
  119.      */
  120.     boolean isMissingKeyUsage() {
  121.         return missingKeyUsage;
  122.     }
  123.  
  124.     /**
  125.      * @param missingIssuerCertificate
  126.      *            the missingIssuerCertificate to set
  127.      */
  128.     void setMissingIssuerCertificate(boolean missingIssuerCertificate) {
  129.         this.missingIssuerCertificate = missingIssuerCertificate;
  130.     }
  131.  
  132.     /**
  133.      * @param missingSerialNumber
  134.      *            the missingSerialNumber to set
  135.      */
  136.     void setMissingSerialNumber(boolean missingSerialNumber) {
  137.         errorCount++;
  138.         this.missingSerialNumber = missingSerialNumber;
  139.     }
  140.  
  141.     /**
  142.      * @param missingSubject
  143.      *            the missingSubject to set
  144.      */
  145.     void setMissingSubject(boolean missingSubject) {
  146.         errorCount++;
  147.         this.missingSubject = missingSubject;
  148.     }
  149.  
  150.     /**
  151.      * @param missingIssuer
  152.      *            the missingIssuer to set
  153.      */
  154.     void setMissingIssuer(boolean missingIssuer) {
  155.         errorCount++;
  156.         this.missingIssuer = missingIssuer;
  157.     }
  158.  
  159.     /**
  160.      * @param missingNotBeforeDate
  161.      *            the missingNotBeforeDate to set
  162.      */
  163.     void setMissingNotBeforeDate(boolean missingNotBeforeDate) {
  164.         errorCount++;
  165.         this.missingNotBeforeDate = missingNotBeforeDate;
  166.     }
  167.  
  168.     /**
  169.      * @param missingNotAfterDate
  170.      *            the missingNotAfterDate to set
  171.      */
  172.     void setMissingNotAfterDate(boolean missingNotAfterDate) {
  173.         errorCount++;
  174.         this.missingNotAfterDate = missingNotAfterDate;
  175.     }
  176.  
  177.     /**
  178.      * @param missingPublicKey
  179.      *            the missingPublicKey to set
  180.      */
  181.     void setMissingPublicKey(boolean missingPublicKey) {
  182.         errorCount++;
  183.         this.missingPublicKey = missingPublicKey;
  184.     }
  185.  
  186.     /**
  187.      * @param badDateRange
  188.      *            the badDateRange to set
  189.      */
  190.     void setBadDateRange(boolean badDateRange) {
  191.         errorCount++;
  192.         this.badDateRange = badDateRange;
  193.     }
  194.  
  195.     /**
  196.      * @param invalidIssuer
  197.      *            the invalidIssuer to set
  198.      */
  199.     void setInvalidIssuer(boolean invalidIssuer) {
  200.         errorCount++;
  201.         this.invalidIssuer = invalidIssuer;
  202.     }
  203.  
  204.     /**
  205.      * @param issuerCannotSignCertificates
  206.      *            the issuerCannotSignCertificates to set
  207.      */
  208.     void setIssuerCannotSignCertificates(boolean issuerCannotSignCertificates) {
  209.         errorCount++;
  210.         this.issuerCannotSignCertificates = issuerCannotSignCertificates;
  211.     }
  212.  
  213.     /**
  214.      * @param unacceptableDateRange
  215.      *            the unacceptableDateRange to set
  216.      */
  217.     void setUnacceptableDateRange(boolean unacceptableDateRange) {
  218.         errorCount++;
  219.         this.unacceptableDateRange = unacceptableDateRange;
  220.     }
  221.  
  222.     /**
  223.      * @param missingKeyUsage
  224.      *            the missingKeyUsage to set
  225.      */
  226.     void setMissingKeyUsage(boolean missingKeyUsage) {
  227.         this.missingKeyUsage = missingKeyUsage;
  228.     }
  229.  
  230.     /**
  231.      * @param unknownIssuer
  232.      *            the unknownIssuer to set
  233.      */
  234.     void setUnknownIssuer(boolean unknownIssuer) {
  235.         this.unknownIssuer = unknownIssuer;
  236.     }
  237. }
package com.otterca.repository.util;

/**
 * IllegalArgumentException that bundles multiple errors into a single
 * exception.
 *
 * @author bgiles@otterca.com
 */
public class X509CertificateBuilderIllegalArgumentException extends IllegalArgumentException {
    private static final long serialVersionUID = 1L;
    private boolean missingIssuerCertificate;
    private boolean missingSerialNumber;
    private boolean missingSubject;
    private boolean missingIssuer;
    private boolean missingNotBeforeDate;
    private boolean missingNotAfterDate;
    private boolean missingPublicKey;
    private boolean badDateRange;
    private boolean invalidIssuer;
    private boolean issuerCannotSignCertificates;
    private boolean unacceptableDateRange;
    private boolean missingKeyUsage;
    private boolean unknownIssuer;
    private int errorCount = 0;

    /**
     * @return the number of errors.
     */
    public int getErrorCount() {
        return errorCount;
    }

    /**
     * @return the missingIssuerCertificate
     */
    public boolean isMissingIssuerCertificate() {
        return missingIssuerCertificate;
    }

    /**
     * @return the missingSerialNumber
     */
    public boolean isMissingSerialNumber() {
        return missingSerialNumber;
    }

    /**
     * @return the missingSubject
     */
    public boolean isMissingSubject() {
        return missingSubject;
    }

    /**
     * @return the missingIssuer
     */
    public boolean isMissingIssuer() {
        return missingIssuer;
    }

    /**
     * @return the missingNotBeforeDate
     */
    public boolean isMissingNotBeforeDate() {
        return missingNotBeforeDate;
    }

    /**
     * @return the missingNotAfterDate
     */
    public boolean isMissingNotAfterDate() {
        return missingNotAfterDate;
    }

    /**
     * @return the missingPublicKey
     */
    public boolean isMissingPublicKey() {
        return missingPublicKey;
    }

    /**
     * @return the badDateRange
     */
    public boolean isBadDateRange() {
        return badDateRange;
    }

    /**
     * @return the invalidIssuer
     */
    public boolean isInvalidIssuer() {
        return invalidIssuer;
    }

    /**
     * @return the issuerCannotSignCertificates
     */
    public boolean isIssuerCannotSignCertificates() {
        return issuerCannotSignCertificates;
    }

    /**
     * @return the unknownIssuer
     */
    boolean isUnknownIssuer() {
        return unknownIssuer;
    }

    /**
     * @return the unacceptableDateRange
     */
    boolean isUnacceptableDateRange() {
        return unacceptableDateRange;
    }

    /**
     * @return the missingKeyUsage
     */
    boolean isMissingKeyUsage() {
        return missingKeyUsage;
    }

    /**
     * @param missingIssuerCertificate
     *            the missingIssuerCertificate to set
     */
    void setMissingIssuerCertificate(boolean missingIssuerCertificate) {
        this.missingIssuerCertificate = missingIssuerCertificate;
    }

    /**
     * @param missingSerialNumber
     *            the missingSerialNumber to set
     */
    void setMissingSerialNumber(boolean missingSerialNumber) {
        errorCount++;
        this.missingSerialNumber = missingSerialNumber;
    }

    /**
     * @param missingSubject
     *            the missingSubject to set
     */
    void setMissingSubject(boolean missingSubject) {
        errorCount++;
        this.missingSubject = missingSubject;
    }

    /**
     * @param missingIssuer
     *            the missingIssuer to set
     */
    void setMissingIssuer(boolean missingIssuer) {
        errorCount++;
        this.missingIssuer = missingIssuer;
    }

    /**
     * @param missingNotBeforeDate
     *            the missingNotBeforeDate to set
     */
    void setMissingNotBeforeDate(boolean missingNotBeforeDate) {
        errorCount++;
        this.missingNotBeforeDate = missingNotBeforeDate;
    }

    /**
     * @param missingNotAfterDate
     *            the missingNotAfterDate to set
     */
    void setMissingNotAfterDate(boolean missingNotAfterDate) {
        errorCount++;
        this.missingNotAfterDate = missingNotAfterDate;
    }

    /**
     * @param missingPublicKey
     *            the missingPublicKey to set
     */
    void setMissingPublicKey(boolean missingPublicKey) {
        errorCount++;
        this.missingPublicKey = missingPublicKey;
    }

    /**
     * @param badDateRange
     *            the badDateRange to set
     */
    void setBadDateRange(boolean badDateRange) {
        errorCount++;
        this.badDateRange = badDateRange;
    }

    /**
     * @param invalidIssuer
     *            the invalidIssuer to set
     */
    void setInvalidIssuer(boolean invalidIssuer) {
        errorCount++;
        this.invalidIssuer = invalidIssuer;
    }

    /**
     * @param issuerCannotSignCertificates
     *            the issuerCannotSignCertificates to set
     */
    void setIssuerCannotSignCertificates(boolean issuerCannotSignCertificates) {
        errorCount++;
        this.issuerCannotSignCertificates = issuerCannotSignCertificates;
    }

    /**
     * @param unacceptableDateRange
     *            the unacceptableDateRange to set
     */
    void setUnacceptableDateRange(boolean unacceptableDateRange) {
        errorCount++;
        this.unacceptableDateRange = unacceptableDateRange;
    }

    /**
     * @param missingKeyUsage
     *            the missingKeyUsage to set
     */
    void setMissingKeyUsage(boolean missingKeyUsage) {
        this.missingKeyUsage = missingKeyUsage;
    }

    /**
     * @param unknownIssuer
     *            the unknownIssuer to set
     */
    void setUnknownIssuer(boolean unknownIssuer) {
        this.unknownIssuer = unknownIssuer;
    }
}

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

Introduction to Digital Certificates, Part 3: X509v3

No Comments

This is part of a series on Digital Certificates.

Now that we know what an “ID” is we can quickly understand the big picture with X509v3 certificates.

A X509v3 certificate must contain:

  • Serial Number: unique for each issuer
  • Subject Distinguished Name: subject’s Name (X500)
  • Issuer Distinguished Name: issuer’s name (X500)
  • Not Before: the earliest date/time the certificate may be used
  • Not After: the last date/time the certificate may be used
  • Public Key: how the certificate is bound to the subject
  • Signature Algorithm: how the issuer’s signature is made
  • Signature: how the certificate is bound to the issuer

As you can see the certificate contains the core characteristics of an ID – it identifies the subject, issuer, a way to identify the subject (the key), a way to prove the issuer produced the certificate (the signature), and immutability (again, the signature).

The serial number has an arbitrary length. Many CAs use a UUID so you must be prepared to handle at least that many digits.

The subject and issuer are identified by X500 names. This is the same format used by LDAP directories and in fact they’re part of the same set of standards. You should try to adhere to conventions even if you will only use your certificates internally – it gives you the best shot at reusing existing software libraries and applications.

The most important X500 name attribute for servers is the “Common Name” (CN) – many applications require it to match the server’s hostname or DNS name.

A “self-signed” certificate uses the same value for subject and issuer.

The “not before date” and “not after date” are self-explanatory. The date range of a certificate should not exceed the date range of the signing certificate.

The “signature” is a cryptographic signature of the contents of the certificate. It is made with the issuer’s private key and can be verified with the issuer’s public key. The issuer’s public key may be known to the client through other trusted means or via PKIX (public certificate authorities). The signature should not be blindly trusted.

Digital certificates are encoded in a well-known predecessor to XML: ASN.1 (Abstract Syntax Notation One). Each field is tagged with a tree of numbers, e.g., “1.2.840.113549.1.1.11″, to identify its contents. These numbers are registered at a central authority so they’re always unique – you don’t have to worry about two companies using the same tag for different purposes.

You should always use a library to read and write ASN.1 documents. The format is difficult to work with and there’s no benefit in rolling your own.

Extensions

X509v3 certificates offer several standard extensions and an issuer can always add its own extensions as well – just remember to go through the formal process to get unique ASN.1 tags.

Extensions can be marked ‘critical’ or ‘noncritical’. The user of a certificate is expected to gracefully handle noncritical extensions but must understand and handle any critical extensions. If a critical extension is not understood the certificate should be refused.

Basic Constraint

This critical extension indicates whether a certificate can be used to sign other certificates. There is no technical reason why you can’t use any certificate to sign another certificate but PKIX policy (discussed later) requires this.

The basic constraint has an optional “certificate path length” that indicates how deep the certificate chain can be below this point. It can be used to delegate authority by creating certificates that can be used to sign normal “leaf” certificates but not additional “signing” certificates.

Subject Alternate Names

This noncritical extension contains one or more alternate name for the subject. The most common uses are email address or IP Address (for servers).

N.B., there is a well-known hack that can embed an email address in an X500 name but this technique is more correct. In practice many clients ignore this extension so you should use both approaches.

Issuer Alternate Names

Same thing, but for issuers instead of subjects.

Key Usage

This critical extension indicates the acceptable usages for this certificate. There are a number of flags but the overall gist is that you can use these flags to indicate whether a certificate should be used to sign other certificates, for servers, or for individuals. You usually want separate certificates for each purpose.

Flags:

  • Digital Signature
  • Nonrepudiation
  • Key Encipherment
  • Data Encipherment
  • Key Agreement
  • Key Certificate Signing
  • CRL Signing
  • Encipher Only
  • Decipher Only

Extended Key Usage

This noncritical extension elaborates on acceptable usages.

Subject Key Identifier

This noncritical extension offers a standard shorthand reference for the certificate’s public key. It is a good searchable index since the public key itself can have arbitrary length.

Authority Key Identifier

This noncritical extension offers a standard shorthand reference for the certificate’s issuer’s public key. This can be used to quickly establish certificate chains.

Search Criteria

There are several search criteria identified by RFC4387. They can give you an idea about what creates a good search key. (Note: a truncated search key is fine for your own use – you only need the full search key if you have a public certificate repository.)

Certificate Hash / Fingerprint

This is the SHA-1 hash of the entire certificate (in DER format). You can easily get this using OpenSSL tools.

Name

The subject’s Common Name.

sHash

The Base64-encoded SHA-1 hash of the subject’s DN, in DER format. OpenSSL provides a truncated form of this in hex.

iHash

The same thing but for the issuer.

iAndSHash

The Base64-encoded SHA-1 hash of the certificate’s issuer and serial number. The last values should be unique.

sKIDHash

The base64-encoded SHA-1 hash of the certificate’s subject key identifier. This hash is on the key identifier itself, not the entire SKID extension.

In the real world…

In the real world clients often do not check or honor the extensions. Certificate authorities may use expired certificates or certificates lacking the basic constraint.

The bottom line is that you have to have very low standards if you accept public certificates. In contrast certificates intended for internal use can and should be carefully checked.

Vulnerabilities and Attacks

The serial number, X500 names, and public key have essentially unlimited length. This must be kept in mind if you accept certificates from the public – malicious certificates have been seen that attempt to trigger buffer overruns in order to execute arbitrary code.

For More Information

RFC2459: Internet X.509 Public Key Infrastructure | Certificate and CRL Profile

RFC4387: Internet X.509 Public Key Infrastructure | Operational Protocols: Certificate Store Access via HTTP

The Legion of the Bouncy Castle

X.509 Public Key Certificate and Certification Request Generation

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

Introduction to Digital Certificates, Part 2: IDs

No Comments

This is part of a series on Digital Certificates.

Before we get into the details of digital certificates we need to take a big step back and ask what an ID means in the big picture.

Any ID has several mandatory characteristics:

1. it is tangible. (Yes, computer files are tangible.)

2. it identifies a “subject”. For instance, the citizen of a passport, the driver of a state driver’s license, the employee, the student.

3. it identifies an “issuer”. In the examples above this is the country issuing the passport, the state issuing the driver’s license, the employer, the school.

4. it has some way to bind the subject to the person or organization. At a minimum it’s a photograph and/or signature, perhaps with basic biometric information like height and weight.

5. it has some way to bind the issuer to the document. For an employee badge or student ID this might just be a logo. For high security documents you may add watermarks, holograms or even laminated layers containing fluorescing content.

6. it is reasonably immutable.

An ID may include additional characteristics:

1. a unique serial number.

2. an expiration date, and possibly a ‘not valid before’ date as well.

3. usage restrictions. (E.g., there may be driving hours restrictions on a driver’s license.)

4. alternate names for the subject or issuer.

Vulnerabilities and Attacks

What are the vulnerabilities of IDs? How can they be attacked? Entire books have been written on this subject but we can quickly hit the highlights.

Impersonation

We can check that the person presenting the ID matches the description on the ID… but how can we trust that the person going to the issuer isn’t impersonating somebody else? It’s a valid ID issued to the wrong person.

What about the issuer itself? How can we trust the issuer is who it claims to be? Consider the classic student fake ID.

Outdated Information/Revocation

What do you do when the subject dies (if a person) or is acquired by another organization (if a business)? What if the student graduates? Drops out?

A variation on this is when the state suspends a person’s driver’s license. It’s no longer valid as proof of authority to drive but it’s still proof of identity. How to you make sure third parties have the most current information?

Tampering and Forgery

An ID should be immutable but it may still be possible to tamper with it. E.g., paste a picture of one person on top of the picture of a different person on a driver’s license. How easy will it be to detect that if you only look at the ID through a plastic sleeve?

This is a key point to remember – fake IDs don’t need to be perfect. They only need to be “good enough” for the intended purpose. That can be a shockingly lower barrier than you expect.

Lack of Due Diligence

This vulnerability is the nastiest – how can you trust the issuer to do a good job? In some cases this is sloppiness, but there’s also the structural questions of who pays the cost of investigating the subject and who pays the cost when the issuer is mistaken. An issuer who is expected to pay the costs for the investigation but will pay no penalty for mistakes isn’t likely to put much effort into the investigation. Make no mistake – they may not make intentional errors. They just won’t be zealous about doing an in-depth investigation.

On the other hand an issuer who faces a serious financial risk if they misidentify the subject will naturally make a much more serious effort to get it right.

Lessons

We can summarize this in two observations:

1. A third-party certificate authority will put its own interests above yours.

2. Your own certificate authority will still need to invest substantial resources to prevent impersonations, tampering, forgery, etc.

The bottom line is that Digital Certificates are a powerful tool but they are only a tool. They are not a silver bullet.

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

Introduction to Digital Certificates, Part 1

No Comments

This is the first entry in a series on the how, what and why of digital certificates. Especially the ‘what’ and ‘why’ since we developers tend to focus too much on the ‘how’.

The tentative topics include

I’m also working on a set of related katana(*) projects. The current topics include

  • writing a certificate builder
  • writing test certificates into a KeyStore
  • storing certs in a JPA database
  • writing dao unit tests using Spring 3.1.1 @Configuration and @Profile beans
  • using X509 certificates with container-provided authentication
  • using X509 certificates with application-provided authentication
  • writing a webapp using Google App Engine (GAE)
  • writing a UI using Google Web Toolkit (GWT)
  • more to follow…

Why GAE and GWT? Because this is a katana and I’ve never worked with GAE and only briefly with GWT. What better time to learn them?

(* A ‘katana’ (which may be the wrong word) in this context is a small programming problem to keep your skills sharp and learn new technologies. This effort is much bigger than a traditional katana but still a modest effort since it’s not intended for production use. Yet.)

Why do we care?

Why do we care about digital certificates? There are four common stories: securing a connection to a server, identifying a client, identifying a stranger, and storing public keys.

Securing a connection to a server

For most people the most common use of digital certificates is the ‘secure’ payment pages on websites. Behind the scenes the browser gets a digital certificate from the server, checks it, and only proceeds to establish an encrypted connection if it’s valid.

Developers can also use certificates to establish secure connections to other services, e.g., to a relational database or mail server.

In practice? Corners are often cut. In the worst case an ill-informed developer won’t perform any validation of the server’s certificate. More conscientious developers may perform additional checks but be forced to ignore the results because of ‘valid’ bad certs in the wild.

Identifying a client

How do you identify your clients? The traditional approach has been username and password but we all know that’s an extremely weak standard. In many environments clients are now required to provide their own digital certificate in order to establish a connection. The details are often hidden in the browser or a ‘smart card’ so the user may be unaware that this is happening.

Identifying a stranger

How do you know who a stranger is? Can you take them at their word? Rarely. Can you find somebody you both trust to vouch for them? Again, rarely. What we’re left with is a third party we both trust, e.g., the government agency that issued the passport or driver’s license. But this has its own limitations – do you know what a Freedonian passport looks like? Do you know how to verify its authenticity?

Public certificate authorities can act as trusted third parties to identify unknown parties. In fact that’s what we do when we use our browser – we shouldn’t trust the website claiming to be amazon.com to really be amazon.com, we want a third party (the certificate authority) to vouch for them.

In contrast we often know who the other party is. We might have been given the necessary certificates earlier via a separate trusted mechanism.

Storing public keys

Sometimes we need to manage our own keys for other reasons. A java “keystore”, or more generally a PKCS12 container, is a very good place to hold the private key. But where do we store the public key? How do we keep track of it if there are several other public keys? How do we provide it to others? How do they keep track of it?

These are all questions that lead to the initial digital certificates. Wrap the public key in a certificate and it’s easy to keep in a java KeyStore or PKCS12 container.

N.B., never store naked keys! Jasypt isn’t good enough! Proper storage of keys is a deep topic but a good start is to always keep your keys in a java or PKCS12 keystore. You then provide the password to the keystore via the appserver, e.g., via a JNDI property. The keystore itself should never be stored in the database. If you don’t have exclusive access to the appserver a good place for a read-only keystore is the webapp’s classpath. You can then load the keystore using the standard classloader methods. It’s more complicated if you need an updateable keystore, e.g., for working keys to minimize the exposure of the long-lived keys.

Sidenote: a recent major breach possibly involving millions of credit card purchases happened because the data was encrypted with a key that was itself kept in the database. Access to the database gave them access to everything they needed to decrypt the data!

What scale do we care about as developers?

On one extreme are very tightly controlled environments with only a few, rarely-changing, certificates. Developers at these sites can find everything they need in a quick google search and/or reading the OpenSSL documents.

One step up are what might be called departmental or even small enterprise environments. These can be large organizations, e.g., a university with tens of thousands of students, facility and staff, but there’s a straightforward hierarchy or well-defined set of hierachies. This is an environment where a small team of developers can do interesting work.

Then there’s the large enterprise and public certificate authority realms. Federated realms, unclear hierarchies, ambiguous legal environments. If you’re working in this environment my blog comments won’t be much help… but hopefully won’t be good for a quick laugh!

Danger, Danger, Will Robinson!

If you read nothing else in this series, read this!

Top 10 PKI Risks

Everything you Never Wanted to Know about PKI but were Forced to Find Out (Peter Gutmann)

Finally there was a recent uproar after it was learned that one CA, TrustWave, was selling a network firewall that used its trusted root certificate to create fake certificates that allowed all encrypted traffic to be intercepted. This is bad when employees check their bank balance at work, it’s far worse when employees are working with legally privileged information (e.g., personal medical or legal issues). Nobody realized that there was a problem since TrustWave was one of the standard root certificates accepted by all of the major browsers – the Firefox and Chrome browsers that people download immediately to replace the corporate-mandated MSIE didn’t give any warnings about untrusted certificates and nobody thought to check the actual certificates.

This drives home the fact that public digital certificates are only as trustworthy as the least trustworthy CA on your list. Check your browser – there’s now typically several hundred trusted CAs by default and all it takes is one of them to have a breach to be able to convince you to hand over your credit card information. Or worse.

Introduction to PostgreSQL/PLJava, part 5: Operations and Indexes

No Comments

This article discusses operators and indexes in PL/Java. Many user-defined types will not need the former but a hash index should be supported for their use in SQL join clauses.

Operators

Operators are normal PL/Java methods that are also marked as operators via the CREATE OPERATOR statement.

Basic arithmetic for rational numbers is supported as

  1.     public static Rational negate(Rational p) throws SQLException {
  2.         if (p == null) {
  3.             return null;
  4.         }
  5.         return new Rational(-p.getNumerator(), p.getDenominator());
  6.     }
  7.  
  8.     public static Rational add(Rational p, Rational q) throws SQLException {
  9.         if ((p == null) || (q == null)) {
  10.             return null;
  11.         }
  12.         BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(
  13.                 BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));
  14.         BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
  15.         BigInteger gcd = n.gcd(d);
  16.         n = n.divide(gcd);
  17.         d = d.divide(gcd);
  18.         return new Rational(n.longValue(), d.longValue());
  19.     }
  20.  
  21.     public static Rational subtract(Rational p, Rational q) throws SQLException {
  22.         if ((p == null) || (q == null)) {
  23.             return null;
  24.         }
  25.         BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).subtract(
  26.                 BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));
  27.         BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
  28.         BigInteger gcd = n.gcd(d);
  29.         n = n.divide(gcd);
  30.         d = d.divide(gcd);
  31.         return new Rational(n.longValue(), d.longValue());
  32.     }
  33.  
  34.     public static Rational multiply(Rational p, Rational q) throws SQLException {
  35.         if ((p == null) || (q == null)) {
  36.             return null;
  37.         }
  38.         BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getNumerator()));
  39.         BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
  40.         BigInteger gcd = n.gcd(d);
  41.         n = n.divide(gcd);
  42.         d = d.divide(gcd);
  43.         return new Rational(n.longValue(), d.longValue());
  44.     }
    public static Rational negate(Rational p) throws SQLException {
        if (p == null) {
            return null;
        }
        return new Rational(-p.getNumerator(), p.getDenominator());
    }

    public static Rational add(Rational p, Rational q) throws SQLException {
        if ((p == null) || (q == null)) {
            return null;
        }
        BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(
                BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));
        BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
        BigInteger gcd = n.gcd(d);
        n = n.divide(gcd);
        d = d.divide(gcd);
        return new Rational(n.longValue(), d.longValue());
    }

    public static Rational subtract(Rational p, Rational q) throws SQLException {
        if ((p == null) || (q == null)) {
            return null;
        }
        BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).subtract(
                BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));
        BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
        BigInteger gcd = n.gcd(d);
        n = n.divide(gcd);
        d = d.divide(gcd);
        return new Rational(n.longValue(), d.longValue());
    }

    public static Rational multiply(Rational p, Rational q) throws SQLException {
        if ((p == null) || (q == null)) {
            return null;
        }
        BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getNumerator()));
        BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));
        BigInteger gcd = n.gcd(d);
        n = n.divide(gcd);
        d = d.divide(gcd);
        return new Rational(n.longValue(), d.longValue());
    }

and

  1.       CREATE FUNCTION javatest.rational_negate(javatest.rational) RETURNS javatest.rational
  2.           AS 'sandbox.Rational.negate'
  3.           LANGUAGE JAVA IMMUTABLE STRICT;
  4.  
  5.       CREATE FUNCTION javatest.rational_add(javatest.rational, javatest.rational)
  6.           RETURNS javatest.rational
  7.           AS 'sandbox.Rational.add'
  8.           LANGUAGE JAVA IMMUTABLE STRICT;
  9.  
  10.       CREATE FUNCTION javatest.rational_subtract(javatest.rational, javatest.rational)
  11.           RETURNS javatest.rational
  12.           AS 'sandbox.Rational.subtract'
  13.           LANGUAGE JAVA IMMUTABLE STRICT;
  14.  
  15.       CREATE FUNCTION javatest.rational_multiply(javatest.rational, javatest.rational)
  16.           RETURNS javatest.rational
  17.           AS 'sandbox.Rational.multiply'
  18.           LANGUAGE JAVA IMMUTABLE STRICT;
  19.  
  20.       CREATE FUNCTION javatest.rational_divide(javatest.rational, javatest.rational)
  21.           RETURNS javatest.rational
  22.           AS 'sandbox.Rational.divide'
  23.           LANGUAGE JAVA IMMUTABLE STRICT;
  24.  
  25.       CREATE OPERATOR - (
  26.          rightarg = javatest.rational, procedure.rational_negate
  27.       );
  28.  
  29.       CREATE OPERATOR + (
  30.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_add,
  31.          commutator = +
  32.       );
  33.  
  34.       CREATE OPERATOR - (
  35.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_subtract
  36.       );
  37.  
  38.       CREATE OPERATOR * (
  39.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide,
  40.          commutator = *
  41.       );
  42.  
  43.       CREATE OPERATOR / (
  44.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide
  45.       );
      CREATE FUNCTION javatest.rational_negate(javatest.rational) RETURNS javatest.rational
          AS 'sandbox.Rational.negate'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_add(javatest.rational, javatest.rational)
          RETURNS javatest.rational
          AS 'sandbox.Rational.add'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_subtract(javatest.rational, javatest.rational)
          RETURNS javatest.rational
          AS 'sandbox.Rational.subtract'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_multiply(javatest.rational, javatest.rational)
          RETURNS javatest.rational
          AS 'sandbox.Rational.multiply'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_divide(javatest.rational, javatest.rational)
          RETURNS javatest.rational
          AS 'sandbox.Rational.divide'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE OPERATOR - (
         rightarg = javatest.rational, procedure.rational_negate
      );

      CREATE OPERATOR + (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_add,
         commutator = +
      );

      CREATE OPERATOR - (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_subtract
      );

      CREATE OPERATOR * (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide,
         commutator = *
      );

      CREATE OPERATOR / (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide
      );

The operator characters are one to 63 characters from the set “+ – * / < > = ~ ! @ # % ^ & | ` ?” with a few restrictions to avoid confusion with the start of SQL comments.

The commutator operator is a second operator (possibly the same) that has the same results if the left and right values are swapped. This is used by the optimizer.

The negator operator is one that the opposite results if the left and right values are swapped. It is only valid on procedures that return a boolean value. Again this is used by the optimizer.

Ordering Operators

Many UDTs can be ordered in some manner. This may be something obvious, e.g., ordering rational numbers, or something a bit more arbitrary, e.g., ordering complex numbers.

We can define ordering operations in the same manner as above. N.B., there is no longer anything special about these operators – with an unfamiliar UDT you can’t assume that < really means “less than”. The sole exception is “!=” which is always rewritten as “” by the parser.

  1.     public static int compare(Rational p, Rational q) {
  2.         if (p == null) {
  3.             return 1;
  4.         } else if (q == null) {
  5.             return -1;
  6.         }
  7.         BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));
  8.         BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));
  9.         return l.compareTo(r);
  10.     }
  11.  
  12.     public int compareTo(Rational p) {
  13.         return compare(this, p);
  14.     }
  15.  
  16.     public static int compare(Rational p, double q) {
  17.         if (p == null) {
  18.             return 1;
  19.         }
  20.         double d = p.doubleValue();
  21.         return (d < q) ? -1 : ((d == q) ? 0 : 1);
  22.     }
  23.  
  24.     public int compareTo(double q) {
  25.         return compare(this, q);
  26.     }
  27.  
  28.     public static boolean lessThan(Rational p, Rational q) {
  29.         return compare(p, q) < 0;
  30.     }
  31.  
  32.     public static boolean lessThanOrEquals(Rational p, Rational q) {
  33.         return compare(p, q) <= 0;
  34.     }
  35.  
  36.     public static boolean equals(Rational p, Rational q) {
  37.         return compare(p, q) = 0;
  38.     }
  39.  
  40.     public static boolean greaterThan(Rational p, Rational q) {
  41.         return compare(p, q) > 0;
  42.     }
  43.  
  44.     public static boolean lessThan(Rational p, double q) {
  45.         if (p == null) {
  46.             return false;
  47.         }
  48.         return p.compareTo(q) < 0;
  49.     }
  50.  
  51.     public static boolean lessThanOrEquals(Rational p, double q) {
  52.         if (p == null) {
  53.             return false;
  54.         }
  55.         return p.compareTo(q) = 0;
  56.     }
  57.  
  58.     public static boolean greaterThan(Rational p, double q) {
  59.         if (p == null) {
  60.             return true;
  61.         }
  62.         return p.compareTo(q) > 0;
  63.     }
    public static int compare(Rational p, Rational q) {
        if (p == null) {
            return 1;
        } else if (q == null) {
            return -1;
        }
        BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));
        BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));
        return l.compareTo(r);
    }

    public int compareTo(Rational p) {
        return compare(this, p);
    }

    public static int compare(Rational p, double q) {
        if (p == null) {
            return 1;
        }
        double d = p.doubleValue();
        return (d < q) ? -1 : ((d == q) ? 0 : 1);
    }

    public int compareTo(double q) {
        return compare(this, q);
    }

    public static boolean lessThan(Rational p, Rational q) {
        return compare(p, q) < 0;
    }

    public static boolean lessThanOrEquals(Rational p, Rational q) {
        return compare(p, q) <= 0;
    }

    public static boolean equals(Rational p, Rational q) {
        return compare(p, q) = 0;
    }

    public static boolean greaterThan(Rational p, Rational q) {
        return compare(p, q) > 0;
    }

    public static boolean lessThan(Rational p, double q) {
        if (p == null) {
            return false;
        }
        return p.compareTo(q) < 0;
    }

    public static boolean lessThanOrEquals(Rational p, double q) {
        if (p == null) {
            return false;
        }
        return p.compareTo(q) = 0;
    }

    public static boolean greaterThan(Rational p, double q) {
        if (p == null) {
            return true;
        }
        return p.compareTo(q) > 0;
    }

Note that I’ve defined methods to compare either two rational numbers or one rational number and one double number.

  1.       CREATE FUNCTION javatest.rational_lt(javatest.rational, javatest.rational)
  2.           RETURNS bool
  3.           AS 'sandbox.Rational.lessThan'
  4.           LANGUAGE JAVA IMMUTABLE STRICT;
  5.  
  6.       CREATE FUNCTION javatest.rational_le(javatest.rational, javatest.rational)
  7.           RETURNS bool
  8.           AS 'sandbox.Rational.lessThanOrEquals'
  9.           LANGUAGE JAVA IMMUTABLE STRICT;
  10.  
  11.       CREATE FUNCTION javatest.rational_eq(javatest.rational, javatest.rational)
  12.           RETURNS bool
  13.           AS 'sandbox.Rational.equals'
  14.           LANGUAGE JAVA IMMUTABLE STRICT;
  15.  
  16.       CREATE FUNCTION javatest.rational_ge(javatest.rational, javatest.rational)
  17.           RETURNS bool
  18.           AS 'sandbox.Rational.greaterThanOrEquals'
  19.           LANGUAGE JAVA IMMUTABLE STRICT;
  20.  
  21.       CREATE FUNCTION javatest.rational_gt(javatest.rational, javatest.rational)
  22.           RETURNS bool
  23.           AS 'sandbox.Rational.greaterThan'
  24.           LANGUAGE JAVA IMMUTABLE STRICT;
  25.  
  26.       CREATE FUNCTION javatest.rational_cmp(javatest.rational, javatest.rational)
  27.           RETURNS int
  28.           AS 'sandbox.Rational.compare'
  29.           LANGUAGE JAVA IMMUTABLE STRICT;
  30.  
  31.       CREATE FUNCTION javatest.rational_lt(javatest.rational, float8)
  32.           RETURNS bool
  33.           AS 'sandbox.Rational.lessThan'
  34.           LANGUAGE JAVA IMMUTABLE STRICT;
  35.  
  36.       CREATE FUNCTION javatest.rational_le(javatest.rational, float8)
  37.           RETURNS bool
  38.           AS 'sandbox.Rational.lessThanOrEquals'
  39.           LANGUAGE JAVA IMMUTABLE STRICT;
  40.  
  41.       CREATE FUNCTION javatest.rational_eq(javatest.rational, float8)
  42.           RETURNS bool
  43.           AS 'sandbox.Rational.equals'
  44.           LANGUAGE JAVA IMMUTABLE STRICT;
  45.  
  46.       CREATE FUNCTION javatest.rational_ge(javatest.rational, float8)
  47.           RETURNS bool
  48.           AS 'sandbox.Rational.greaterThanOrEquals'
  49.           LANGUAGE JAVA IMMUTABLE STRICT;
  50.  
  51.       CREATE FUNCTION javatest.rational_gt(javatest.rational, float8)
  52.           RETURNS bool
  53.           AS 'sandbox.Rational.greaterThan'
  54.           LANGUAGE JAVA IMMUTABLE STRICT;
  55.  
  56.       CREATE OPERATOR < (
  57.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,
  58.          commutator = > , negator = >= ,
  59.          restrict = scalarltsel, join = scalarltjoinsel, merges
  60.       );
  61.  
  62.       CREATE OPERATOR <= (
  63.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,
  64.          commutator = >= , negator = > ,
  65.          restrict = scalarltsel, join = scalarltjoinsel, merges
  66.       );
  67.  
  68.       CREATE OPERATOR = (
  69.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_eq,
  70.          commutator = = , negator = <>, hashes, merges
  71.       );
  72.  
  73.       CREATE OPERATOR >= (
  74.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,
  75.          commutator = <= , negator = < ,
  76.          restrict = scalarltsel, join = scalarltjoinsel, merges
  77.       );
  78.  
  79.       CREATE OPERATOR > (
  80.          leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,
  81.          commutator = <= , negator = < ,
  82.          restrict = scalargtsel, join = scalargtjoinsel, merges
  83.       );
  84.  
  85.       CREATE OPERATOR < (
  86.          leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_lt,
  87.          commutator = > , negator = >=
  88.       );
  89.  
  90.       CREATE OPERATOR <= (
  91.          leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_le,
  92.          commutator = >= , negator = >
  93.       );
  94.  
  95.       CREATE OPERATOR = (
  96.          leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_eq,
  97.          commutator = = , negator = <>
  98.       );
  99.  
  100.       CREATE OPERATOR >= (
  101.          leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_ge,
  102.          commutator = <= , negator = <
  103.       );
  104.  
  105.       CREATE OPERATOR > (
  106.          leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_gt,
  107.          commutator = < , negator = <=
  108.       );
      CREATE FUNCTION javatest.rational_lt(javatest.rational, javatest.rational)
          RETURNS bool
          AS 'sandbox.Rational.lessThan'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_le(javatest.rational, javatest.rational)
          RETURNS bool
          AS 'sandbox.Rational.lessThanOrEquals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_eq(javatest.rational, javatest.rational)
          RETURNS bool
          AS 'sandbox.Rational.equals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_ge(javatest.rational, javatest.rational)
          RETURNS bool
          AS 'sandbox.Rational.greaterThanOrEquals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_gt(javatest.rational, javatest.rational)
          RETURNS bool
          AS 'sandbox.Rational.greaterThan'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_cmp(javatest.rational, javatest.rational)
          RETURNS int
          AS 'sandbox.Rational.compare'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_lt(javatest.rational, float8)
          RETURNS bool
          AS 'sandbox.Rational.lessThan'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_le(javatest.rational, float8)
          RETURNS bool
          AS 'sandbox.Rational.lessThanOrEquals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_eq(javatest.rational, float8)
          RETURNS bool
          AS 'sandbox.Rational.equals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_ge(javatest.rational, float8)
          RETURNS bool
          AS 'sandbox.Rational.greaterThanOrEquals'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE FUNCTION javatest.rational_gt(javatest.rational, float8)
          RETURNS bool
          AS 'sandbox.Rational.greaterThan'
          LANGUAGE JAVA IMMUTABLE STRICT;

      CREATE OPERATOR < (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,
         commutator = > , negator = >= ,
         restrict = scalarltsel, join = scalarltjoinsel, merges
      );

      CREATE OPERATOR <= (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,
         commutator = >= , negator = > ,
         restrict = scalarltsel, join = scalarltjoinsel, merges
      );

      CREATE OPERATOR = (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_eq,
         commutator = = , negator = <>, hashes, merges
      );

      CREATE OPERATOR >= (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,
         commutator = <= , negator = < ,
         restrict = scalarltsel, join = scalarltjoinsel, merges
      );

      CREATE OPERATOR > (
         leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,
         commutator = <= , negator = < ,
         restrict = scalargtsel, join = scalargtjoinsel, merges
      );

      CREATE OPERATOR < (
         leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_lt,
         commutator = > , negator = >=
      );

      CREATE OPERATOR <= (
         leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_le,
         commutator = >= , negator = >
      );

      CREATE OPERATOR = (
         leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_eq,
         commutator = = , negator = <>
      );

      CREATE OPERATOR >= (
         leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_ge,
         commutator = <= , negator = <
      );

      CREATE OPERATOR > (
         leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_gt,
         commutator = < , negator = <=
      );

Restrict is an optimization estimator procedure. It’s usually safe to use the appropriate standard procedure.

Join is an optimization estimator procedure. It’s usually safe to use the appropriate standard procedure.

Hashes indicates that the operator can be used in hash joins.

Merges indicates that the operator can be used in merge joins.

Indexes

Indexes are used in three places – to enforce uniqueness constraints and to speed up WHERE and JOIN clauses.

  1.     -- btree join
  2.   CREATE OPERATOR CLASS rational_ops
  3.       DEFAULT FOR TYPE javatest.rational USING btree AS
  4.         OPERATOR        1       < ,
  5.         OPERATOR        2       <= ,
  6.         OPERATOR        3       = ,
  7.         OPERATOR        4       >= ,
  8.         OPERATOR        5       > ,
  9.         FUNCTION        1       javatest.rational_cmp(javatest.rational, javatest.rational);
  10.  
  11.     -- hash join
  12.    CREATE OPERATOR CLASS rational_ops
  13.       DEFAULT FOR TYPE javatest.rational USING hash AS
  14.         OPERATOR        1       = ,
  15.         FUNCTION        1       javatest.rational_hashCode(javatest.rational);
    -- btree join
  CREATE OPERATOR CLASS rational_ops
      DEFAULT FOR TYPE javatest.rational USING btree AS
        OPERATOR        1       < ,
        OPERATOR        2       <= ,
        OPERATOR        3       = ,
        OPERATOR        4       >= ,
        OPERATOR        5       > ,
        FUNCTION        1       javatest.rational_cmp(javatest.rational, javatest.rational);

    -- hash join
   CREATE OPERATOR CLASS rational_ops
      DEFAULT FOR TYPE javatest.rational USING hash AS
        OPERATOR        1       = ,
        FUNCTION        1       javatest.rational_hashCode(javatest.rational);

Operator Families

Finally, PostgreSQL has the concept of “Operator Families” that group related operator classes under a single umbrella. For instance you might have one family that supports cross-comparison between int2, int4 and int8 values. Each can be specified individually but by creating an operator family you give a few more hints to the PostgreSQL optimizer.

More Information

CREATE OPERATOR (PostgreSQL)

CREATE OPERATOR CLASS (PostgreSQL)

CREATE OPERATOR FAMILY (PostgreSQL)

Operator Optimization (PostgreSQL)

Interfacing Extensions To Indexes (PostreSQL)

Blue Taste Theme created by Jabox