Package org.bzdev.net

Class SecureBasicUtilities

java.lang.Object
org.bzdev.net.SecureBasicUtilities

public class SecureBasicUtilities extends Object
Operations for basic authentication with digital signatures and timestamps.

This is a support class for an authentications scheme dubbed Secure Basic Authentication. As a protocol, secure basic authentication is identical to basic authentication as described in RFC 7617. The differences are in how passwords are created and compared, and in how realms are named. Generally, a secure-basic-authentication password is a URL-safe, base 64 encoding of a sequence of bytes. The first four bytes is a timestamp of a 32-bit two's complement integer, stored in little-endian byte order, providing the time at which the password was created as the number of seconds since 1970-01-01T00:00:00Z. The next four bytes is a 32-bit CRC of the first 4 bytes of the sequence followed by the password as an array of bytes using the UTF-8 character encoding. The remainder of the sequence is either

  1. a SHA-256 message digest of (1) the first eight bytes of the sequence and (2) a password using the UTF-8 character encoding.
  2. a digital signature of (1) the first eight bytes of the sequence and (2) a password using the UTF-8 character encoding.
  3. a digital signature of (1) the first eight bytes of the sequence, (2) the DER encoding of the public key provided in an SSL certificate, and (3) a password using the UTF-8 character encoding.
In all cases, both sides of the connection must use the same password. When a digital signature is used, the server must store a user's public key and the name of the algorithm used to create the signature. To distinguish these cases, the realm (described in RFC 7616) is prefaced with the following:
  1. [D]. This corresponds to Case 1 above. The method iconedRealm(String) will replace this sequence with the Unicode character whose codepoint is 0x231A, which looks like a watch.
  2. [S]. This corresponds to Case 2 above. The method iconedRealm(String) will replace this sequence with the Unicode character whose codepoint is 0x1F58A, which looks like a pen.
  3. [SC]. This corresponds to Case 3 above. The method iconedRealm(String) will replace this sequence with the Unicode character whose codepoint is 0x1F85, followed by the Unicode character whose code point is 0x1F512. This combination looks like a pen followed by a lock.
These emoji are followed optionally by a space (but a space is mandatory if the realm for some reason starts with a space). Emoji are used for two reasons:
  • it is very unlikely for anyone to use an emoji as part of the name of a realm.
  • emoji are easily distinguished from text. It is easy to create a "helper" application that can create the password used in secure basic authentication and paste that password into a password field provided by a browser or other program that does not support secure basic authentication, and the emoji will allow a user to determine which type of authentication is expected.
Case 3 is the most secure. When an SSL or TLS connection is established, a server will provide a client with the server's certificate and the SSL or TLS protocol will ensure that the server that provided the certificate has that certificate's private key. This can stop a variety of man-in-the-middle and spoofing attempts, at least to the point of inadvertently disclosing login credentials: in either case the certificate will be different, which means that the password that is generated will not be useful. A CRC is used because this provides a cheap way of rejecting authentication attempts that have the wrong password.

A client can create a PEM-encoded key pair by calling createPEMPair(), which returns an array containing two strings. The first is a PEM encoded private key and the second is a PEM encoded public key. A user's PEM encoded public key can be provided to a server along with a password. To create a password a client will call the constructor SecureBasicUtilities(String), using its PEM-encoded private key as an argument, and then the method createPassword(Certificate, char[]) to create the password to be given to the server. In this case the first argument is the certificate provided by the server and the second argument is the user's password.

A server will call SecureBasicUtilities(String) using the PEM-encoded public key for a client as an argument. It will decode the password provided by the user by calling decodePassword(String), and then getTimeDiff(byte[]) to check a timestamp. Then it will call checkPassword(byte[],Certificate,String) with the decoded password as its first argument, the server's certificate as its second argument, and the stored password for the user as its third argument.

Finally, there are several 'utility' methods for handling realms, and both a constructor and a method for the case where a key store is used to hold the keys.

NOTE: For compatibility with openssl, one should use the keytool program, or createPEMPair(File,String,String,String,String,char[]), to generate a key pair as a PKCS #12 file will then be created. The openssl equivalent to


 keytool -genkey -keyalg EC -groupname secp256r1 \
         -sigalg SHA256withECDSA -dname CN=nobody@nowhere.com \
         -alias key -keypass password -storepass password \
         -keystore ecstore.pfx
 
is

  openssl ecparam -name prime256v1 -genkey -noout -out eckey.pem
  openssl req -new -x509 -key eckey.pem -out eccert.pem -days 360
  openssl pkcs12 -export -inkey eckey.pem -in eccert.pem \
          -name key -out ecstore.pfx
 
although the choice of a signature algorithm (used to self sign) may be different. To add to the confusion, for the elliptic curve used in this example, keytool prefers the name secp256r1 whereas openssl prefers prime256v1. When openssl is given the name secp256r1, it will indicate that is is using prime256v1, whereas when keytool is given the name prime256v1, it generates an error message. Also keytool must use the same password for the file as for each entry it stores if the file is to be compatible with openssl.
  • Constructor Details

    • SecureBasicUtilities

      public SecureBasicUtilities()
      Constructor for the message-digest case.
    • SecureBasicUtilities

      public SecureBasicUtilities(String pem) throws IOException, IllegalArgumentException, GeneralSecurityException
      Constructor given a string containing PEM-encoded data. The encoded string will start with a header, signature-algorithm, that provides the name of a signature algorithm used in creating or verifying digital signatures. This header's textual representation consists of a line starting with the string "signature-algorithm:", followed by optional white space and the name of the signature algorithm. The default signature algorithm is named SHA256withECDSA. The header will be followed by PEM encoded data whose type is either CERTIFICATE, EC PUBLIC KEY, or EC PRIVATE KEY (while EC is the default and is preferred for performance reasons, some other algorithm such as RSA can be used instead. can be used if desired, in which case 'EC' should be replaced with the name of the algorithm).

      Note: when this constructor is used to read a private key, the key must be DER encoded as is done with Key.getEncoded(): Java makes it difficult to directly read a private key, preferring the use of a key store instead.

      Parameters:
      pem - the PEM string.
      Throws:
      IOException
      IllegalArgumentException
      GeneralSecurityException
    • SecureBasicUtilities

      Constructor given an input stream. The input stream will start with a header, signature-algorithm, that provides the name of a signature algorithm used in creating or verifying digital signatures. This header's textual representation consists of a line starting with the string "signature-algorithm:", followed by optional whitespace and the name of the signature algorithm. The header will be followed by PEM encoded data whose type is either CERTIFICATE, EC PUBLIC KEY, or EC PRIVATE KEY (while EC is the default and is preferred for performance reasons) some other algorithm such as RSA can be used instead. can be used if desired). The data must be UTF-8 encoded.
      Parameters:
      is - the input stream
      Throws:
      IOException
      IllegalArgumentException
      GeneralSecurityException
    • SecureBasicUtilities

      public SecureBasicUtilities(File file, String sigalg, String alias, char[] pwarray) throws IOException, IllegalArgumentException, GeneralSecurityException
      Constructor given a key store. The key store should use the PKCS#12 format, but this is not required. The same password has to be used for the keystore itself and for the private key corresponding to the alias.

      This constructor is particularly useful if the keys are created using a program such as openssl: with the exception of some special cases, Java makes it difficult to read in private keys without using a key store.

      Parameters:
      file - the file containing the key store
      sigalg - the signature algorithm (e.g., SHA256withECDSA); null for the default
      alias - a name used to look up a key or certificate
      pwarray - an array of characters providing a password
      Throws:
      IOException
      IllegalArgumentException
      GeneralSecurityException
  • Method Details

    • getMode

      public static SecureBasicUtilities.Mode getMode(String realm)
      Get the mode for from a string representing an encoded realm. When the return value is not SecureBasicUtilities.Mode.PASSWORD, an encoded realm will have started with one or two emojis, optionally followed by a space.
      Parameters:
      realm - the encoded realm.
      Returns:
      the mode SecureBasicUtilities.Mode.DIGEST, SecureBasicUtilities.Mode.SIGNATURE_WITHOUT_CERT, SecureBasicUtilities.Mode.SIGNATURE_WITH_CERT, or SecureBasicUtilities.Mode.PASSWORD
      See Also:
    • encodeRealm

      public static String encodeRealm(String realm, SecureBasicUtilities.Mode mode)
      Get the encoded realm for from a string representing a realm. When the mode value is not SecureBasicUtilities.Mode.PASSWORD, the encoded realm will have started with one or two emojis, followed by a space:
      • for SecureBasicUtilities.Mode.DIGEST, the encoded string starts with the Unicode character whose code point is 0x231A. This symbol looks like a watch to indicate that the digest uses a time field in addition to a signature.
      • for SecureBasicUtilities.Mode.SIGNATURE_WITHOUT_CERT, the encoded string starts with the Unicode character whose code point is 0x1F58A. This symbol looks like a pen to indicate that a digital signature used.
      • for SecureBasicUtilities.Mode.SIGNATURE_WITH_CERT, the encoded string starts with the Unicode character whose code point is 0x1F58A, followed by the Unicode character whose code point is 0x1F512. The first symbol looks like a pen to indicate that a digital signature used, and the second looks like a lock to indicate that the public key from an SSL/TLS certificate is included in the signature.
      Parameters:
      realm - the realm
      mode - the mode determining the type of encoding (SecureBasicUtilities.Mode.DIGEST, SecureBasicUtilities.Mode.SIGNATURE_WITHOUT_CERT, SecureBasicUtilities.Mode.SIGNATURE_WITH_CERT, or SecureBasicUtilities.Mode.PASSWORD)
      Returns:
      the encoded realm
      See Also:
    • decodeRealm

      public static String decodeRealm(String realm)
      Get the realm for from a string representing an encoded realm. When the value of the mode used when the realm was encoded is not SecureBasicUtilities.Mode.PASSWORD, the encoded realm will have started with one or two emojis, followed optionally by a space.
      Parameters:
      realm - the realm
      Returns:
      the encoded realm
      See Also:
    • iconedRealm

      public static String iconedRealm(String realm)
    • getType

      public SecureBasicUtilities.Type getType()
      Get the type of for this instance of SecureBasicUtilities. The type determines if whether or not a public key and/or a private key is available.
      Returns:
      the type (SecureBasicUtilities.Type.PUBLIC,SecureBasicUtilities.Type.PRIVATE SecureBasicUtilities.Type.BOTH, or SecureBasicUtilities.Type.NONE
      See Also:
    • getPrivateKey

      public PrivateKey getPrivateKey()
      Get the private key for this instance of SecureBasicUtilities.
      Returns:
      the private key; null if there is none
    • getPublicKey

      public PublicKey getPublicKey()
      Get the public key for this instance of SecureBasicUtilities.
      Returns:
      the public key; null if there is none
    • getEncryptionAlgorithm

      public String getEncryptionAlgorithm()
      Get the encryption algorithm used for the public and/or private keys for this instance of SecureBasicUtilities.
      Returns:
      the encryption algorithm
    • getSignatureAlgorithm

      public String getSignatureAlgorithm()
      Get the signature algorithm used for the public and/or private keys for this instance of SecureBasicUtilities.
      Returns:
      the signature algorithm
    • getSigner

      public Signature getSigner() throws GeneralSecurityException
      Get an initialized Signature for signing. The caller should use the Signature methods named update and sign to create the signature.
      Returns:
      an initialized Signature; null if there this object was not created with a private key
      Throws:
      GeneralSecurityException - if the private key is not valid
      See Also:
    • getVerifier

      public Signature getVerifier() throws GeneralSecurityException
      Get an initialized Signature for verification. The caller should use the Signature methods named update and verify to verify the signature.
      Returns:
      an initialized Signature; null if there this object was not created with a public key
      Throws:
      GeneralSecurityException - if the public key is not valid
      See Also:
    • createPassword

      public char[] createPassword(Certificate cert, char[] password) throws GeneralSecurityException, UnsupportedEncodingException
      Create a password based on digital signatures or message digests. This method should not be used when the user-supplied password is used as is.
      Parameters:
      cert - a certificate; null when a certificate is not used.
      password - the user-supplied password
      Returns:
      the encoded password
      Throws:
      GeneralSecurityException
      UnsupportedEncodingException
    • decodePassword

      public static byte[] decodePassword(String password)
      Decode an encoded password represented as a string. This method removes base 64, URL encoding.
      Parameters:
      password - the encoded password
      Returns:
      the bytes that were base 64, URL encoded
    • decodePassword

      public static byte[] decodePassword(byte[] password)
      Decode an encoded password represented as an array of bytes. THis method removes base 64, URL encoding
      Parameters:
      password - the encoded password using the US ASCII subset of UTF-8
      Returns:
      the bytes that were base 64, URL encoded
    • decodePassword

      public static byte[] decodePassword(char[] password)
      Decode an encoded password represented as an array of chars. THis method removes base 64, URL encoding
      Parameters:
      password - the encoded password using the US ASCII subset of UTF-8
      Returns:
      the bytes that were base 64, URL encoded
    • getTimeDiff

      public static int getTimeDiff(byte[] sigarray)
      Given a decoded password, find the time difference between the current time and the password's timestamp. A small negative value can occur when clocks are not synchronized. When the exact time is used, the value should be non-negative as the password will be checked after it is created.
      Parameters:
      sigarray - the decoded password
      Returns:
      the difference between the current time and the password's timestamp in units of seconds
      See Also:
    • checkPassword

      public boolean checkPassword(byte[] sigarray, Certificate cert, String password) throws GeneralSecurityException
      Determine if a password is valid.
      Parameters:
      sigarray - the decoded password
      cert - the certificate used when a password is created; null for digest authentication
      Returns:
      true if the password is valid; false otherise
      Throws:
      GeneralSecurityException
      See Also:
    • getPEMStrings

      public String[] getPEMStrings()
      Get PEM-encoded strings representing public and private keys with the signature algorithm as a header.
      Returns:
      an array whose first element contains the PEM-encoded private key and whose second element contsins the PEM encoded public key, with either null if the key is not present
    • createPEMPair

      public static String[] createPEMPair()
      Generate two PEM encoded strings for a key pair, each with a header specifying a signature algorithm. The header will be "signature-algorithm: SHA256withECDSA" and the type of the PEM headers will be "EC PRIVATE KEY" and "EC PUBLIC KEY".
      Returns:
      an array whose first element contains the PEM-encoded private key and whose second element contsins the PEM encoded public key
    • createPEMPair

      public static String[] createPEMPair(String pspec, String sigalg) throws IllegalArgumentException, GeneralSecurityException
      Generate two PEM encoded strings for a key pair, specifying an elliptic curve and a signature algorithm. The header will be "signature-algorithm: " followed by the name of the signature algorithm, and the type of the PEM headers will be "EC PRIVATE KEY" and "EC PUBLIC KEY". The elliptic curve names and the signature-algorithm names are those recognized by Java.
      Parameters:
      pspec - the name of the elliptic curve, null for the default (secp256r1)
      sigalg - the name of the signature algorithm, null for the default (SHA256withECDSA)
      Returns:
      an array whose first element contains the PEM-encoded private key and whose second element contsins the PEM encoded public key
      Throws:
      IllegalArgumentException
      GeneralSecurityException
    • createPEMPair

      public static String[] createPEMPair(File keystoreFile, String pspec, String sigalg, String alias, String dn, char[] pwarray) throws IllegalArgumentException, GeneralSecurityException, IOException
      Generate two PEM encoded strings for a key pair, specifying an elliptic curve and a signature algorithm, storing the keys in a PKCS #12 file. The header will be "signature-algorithm: " followed by the name of the signature algorithm, and the type of the PEM headers will be "EC PRIVATE KEY" and "EC PUBLIC KEY". The elliptic curve names and the signature-algorithm names are those recognized by Java.
      Parameters:
      keystoreFile - the PKCS #12 file, which will be created if necessary
      pspec - the name of the elliptic curve, null for the default (secp256r1)
      sigalg - the name of the signature algorithm, null for the default (SHA256withECDSA)
      alias - an identifier used to name the key
      dn - a distinguished name for the certificate corresponding to the public key (for example, CN=nobody@nowhere.com).
      pwarray - a character array containing the password to use for the key-store file and to recover the private key given the alias
      Returns:
      an array whose first element contains the PEM-encoded private key and whose second element contsins the PEM encoded public key
      Throws:
      IllegalArgumentException
      GeneralSecurityException
      IOException