![JAR search and dependency download from the Maven repository](/logo.png)
bali.notary.V1NotarizationProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-bali-digital-notary Show documentation
Show all versions of java-bali-digital-notary Show documentation
This project defines the Java interfaces and classes for a digital notary.
/************************************************************************
* Copyright (c) Crater Dog Technologies(TM). All Rights Reserved. *
************************************************************************
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. *
* *
* This code is free software; you can redistribute it and/or modify it *
* under the terms of The MIT License (MIT), as published by the Open *
* Source Initiative. (See http://opensource.org/licenses/MIT) *
************************************************************************/
package bali.notary;
import craterdog.primitives.BinaryString;
import craterdog.primitives.Tag;
import craterdog.primitives.VersionString;
import craterdog.security.MessageCryptex;
import craterdog.security.RsaAesMessageCryptex;
import craterdog.utils.RandomUtils;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import javax.security.auth.DestroyFailedException;
import org.joda.time.DateTime;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
/**
* This class can be used to sign and verify digital notarized documents using a private/public
* key pair. It uses RSA-2048 for asymmetric (public/private) key signature generation and
* verification.
*
* @author Derk Norton
*/
public final class V1NotarizationProvider implements Notarization {
static private final XLogger logger = XLoggerFactory.getXLogger(V1NotarizationProvider.class);
static private final MessageCryptex cryptex = new RsaAesMessageCryptex();
/**
* The algorithm used to generate hash values for the documents.
*/
static public final String HASHING_ALGORITHM = cryptex.getHashAlgorithm();
/**
* The algorithm used to sign and verify the documents.
*/
static public final String SIGNING_ALGORITHM = cryptex.getAsymmetricSignatureAlgorithm();
/**
* The algorithm used to sign and verify the documents.
*/
static public final String ENCRYPTION_ALGORITHM = cryptex.getAsymmetricEncryptionAlgorithm();
/**
* The major version number of the implementation of this digital notary.
*/
static public final int MAJOR_VERSION = 1;
/**
* The minor version number of the implementation of this digital notary.
*/
static public final int MINOR_VERSION = 0;
@Override
public NotaryKey generateKey(char[] passPhrase) {
logger.entry(); // don't log the passPhrase!
logger.debug("Generating a new notary key...");
NotaryKey previousNotaryKey = null;
NotaryKey notaryKey = generateNotaryKey(passPhrase, previousNotaryKey);
logger.exit(notaryKey);
return notaryKey;
}
@Override
public boolean enableKey(NotaryKey notaryKey, char[] passPhrase) {
try {
notaryKey.privateKey = cryptex.decodePrivateKey(notaryKey.encodedKey, passPhrase);
} catch (Exception e) {
logger.error("Unable to decode the private key for a notary key.", e);
return false;
}
return true;
}
@Override
public void disableKey(NotaryKey notaryKey) {
if (notaryKey.privateKey != null) {
try { notaryKey.privateKey.destroy(); } catch (DestroyFailedException ex) { }
}
notaryKey.privateKey = null;
}
@Override
public NotaryKey renewKey(NotaryKey notaryKey, char[] passPhrase) {
logger.entry(notaryKey); // don't log the passPhrase!
logger.debug("Renewing an existing notary key...");
NotaryKey newNotaryKey = generateNotaryKey(passPhrase, notaryKey);
logger.exit(newNotaryKey);
return newNotaryKey;
}
@Override
public NotarizedDocument notarizeDocument(D document, NotaryKey notaryKey) {
logger.entry(document, notaryKey);
logger.debug("Creating the notary certificate citation...");
PrivateKey privateKey = notaryKey.privateKey;
NotarizedDocument notaryCertificate = notaryKey.notaryCertificate;
logger.debug("Filling in the document attributes...");
document.accountTag = notaryCertificate.document.accountTag;
document.timestamp = DateTime.now();
document.certificateCitation = notaryKey.certificateCitation;
logger.debug("Notarizing the document with the notary key...");
NotarizedDocument notarizedDocument = new NotarizedDocument<>();
notarizedDocument.document = document;
BinaryString notarySeal = generateNotarySeal(document, privateKey);
notarizedDocument.notarySeal = notarySeal;
logger.exit(notarizedDocument);
return notarizedDocument;
}
@Override
public void validateDocument(NotarizedDocument extends Document> notarizedDocument, Certificate certificate, Map errors) {
logger.entry(notarizedDocument, certificate, errors);
int errorCount = errors.size(); // record it to see if it changes
logger.debug("Validating the notary certificate...");
validateCertificate(certificate, errors);
if (errorCount == errors.size()) {
// no new errors, so certificate should be valid
logger.debug("Validating the notarized document...");
PublicKey publicKey = certificate.publicKey;
validateNotarizedDocument(notarizedDocument, publicKey, errors);
}
logger.exit(errors);
}
@Override
public DocumentCitation generateCitation(URI reference, NotarizedDocument extends Document> notarizedDocument) {
logger.entry(notarizedDocument);
DocumentCitation citation = generateDocumentCitation(reference, notarizedDocument);
logger.exit(citation);
return citation;
}
@Override
public void validateCitation(NotarizedDocument extends Document> notarizedDocument, DocumentCitation documentCitation, Map errors) {
logger.entry(documentCitation, notarizedDocument, errors);
int errorCount = errors.size(); // record it to see if it changes
if (documentCitation == null) {
logger.error("The document citation is missing...");
errors.put("document.citation.is.missing", documentCitation);
}
if (notarizedDocument == null) {
logger.error("The notarized document is missing...");
errors.put("notarized.document.is.missing", notarizedDocument);
}
if (errorCount == errors.size()) {
// no new errors, so parameters should be valid
validateDocumentCitation(documentCitation, notarizedDocument, errors);
}
logger.exit(errors);
}
@Override
public void throwExceptionOnErrors(String messageTag, Map errors) throws ValidationException {
logger.entry(messageTag, errors);
if (!errors.isEmpty()) {
logger.error("A validation exception \"" + messageTag + "\" was thrown with the following errors: {}", errors);
throw new ValidationException(messageTag, errors, null);
}
logger.exit();
}
static private NotaryKey generateNotaryKey(char[] passPhrase, NotaryKey previousKey) {
logger.debug("Generating a new RSA key pair...");
char[] copyOfPassPhrase = Arrays.copyOf(passPhrase, passPhrase.length);
KeyPair keyPair = cryptex.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String pemValue = cryptex.encodePrivateKey(privateKey, passPhrase);
String encodedKey = "\n" + pemValue + "\n";
logger.debug("Slicing the private key using the passPhrase...");
byte[] passPhraseHash = hashOfPassphrase(copyOfPassPhrase);
BinaryString privateSlice = generatePrivateSlice(privateKey, passPhraseHash);
logger.debug("Wrapping the public key in a public certificate...");
NotarizedDocument notaryCertificate = generateNotaryCertificate(publicKey, privateKey, privateSlice, previousKey);
logger.debug("Generating a document citation to the public certificate...");
Tag certificateTag = notaryCertificate.document.documentTag;
VersionString certificateVersion = notaryCertificate.document.documentVersion;
URI certificateReference = URI.create("bali:/" + certificateTag + "." + certificateVersion);
DocumentCitation certificateCitation = generateDocumentCitation(certificateReference, notaryCertificate);
logger.debug("Populating the new notary key...");
NotaryKey notaryKey = new NotaryKey();
notaryKey.certificateCitation = certificateCitation;
notaryKey.notaryCertificate = notaryCertificate;
notaryKey.privateKey = privateKey;
notaryKey.encodedKey = encodedKey;
return notaryKey;
}
static private BinaryString generatePrivateSlice(PrivateKey privateKey, byte[] hashBytes) {
byte[] privateKeyBytes = null;
byte[] randomBytes = null;
try {
SecureRandom generator = new SecureRandom(hashBytes);
privateKeyBytes = privateKey.getEncoded();
randomBytes = new byte[privateKeyBytes.length];
generator.nextBytes(randomBytes);
byte[] sliceBytes = xorOfByteArrays(privateKeyBytes, randomBytes);
BinaryString privateSlice = new BinaryString(sliceBytes);
return privateSlice;
} catch (Exception e) {
RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to generate the private slice.", e);
throw logger.throwing(exception);
} finally {
// null out sensitive byte arrays
if (hashBytes != null) Arrays.fill(hashBytes, (byte) 0);
if (privateKeyBytes != null) Arrays.fill(privateKeyBytes, (byte) 0);
if (randomBytes != null) Arrays.fill(randomBytes, (byte) 0);
}
}
static private Certificate generateCertificate(Tag accountTag, Tag documentTag, VersionString documentVersion, PublicKey publicKey, BinaryString privateSlice, DocumentCitation previousCertificate) {
URI documentType = URI.create("bali:/?name=bali.account.model.Certificate&version=1");
Certificate certificate = new Certificate();
certificate.accountTag = accountTag;
certificate.documentType = documentType;
certificate.documentTag = documentTag;
certificate.documentVersion = documentVersion;
certificate.timestamp = DateTime.now();
certificate.certificateCitation = previousCertificate;
certificate.previousDocument = previousCertificate;
certificate.signingAlgorithm = SIGNING_ALGORITHM;
certificate.encryptionAlgorithm = ENCRYPTION_ALGORITHM;
certificate.publicKey = publicKey;
certificate.privateSlice = privateSlice;
return certificate;
}
static private NotarizedDocument generateNotaryCertificate(PublicKey publicKey, PrivateKey privateKey, BinaryString privateSlice, NotaryKey previousKey) {
logger.debug("Gathering the certificate attributes...");
Tag accountTag;
Tag documentTag;
VersionString documentVersion;
DocumentCitation previousDocument;
if (previousKey == null) {
accountTag = new Tag();
documentTag = new Tag();
documentVersion = new VersionString(1);
previousDocument = null;
} else {
Certificate previousCertificate = previousKey.notaryCertificate.document;
accountTag = previousCertificate.accountTag;
documentTag = previousCertificate.documentTag;
VersionString previousDocumentVersion = previousCertificate.documentVersion;
documentVersion = VersionString.getNextVersion(previousDocumentVersion);
previousDocument = previousKey.certificateCitation;
}
logger.debug("Creating the new certificate...");
Certificate certificate = generateCertificate(accountTag, documentTag, documentVersion, publicKey, privateSlice, previousDocument);
logger.debug("Notarizing the new certificate...");
BinaryString notarySeal;
if (previousKey != null) {
// must be notarized by the previous notary key if one exists
notarySeal = generateNotarySeal(certificate, previousKey.privateKey);
} else {
// otherwise it is a self signed certificate
notarySeal = generateNotarySeal(certificate, privateKey);
}
NotarizedDocument notaryCertificate = new NotarizedDocument<>();
notaryCertificate.document = certificate;
notaryCertificate.notarySeal = notarySeal;
return notaryCertificate;
}
static private BinaryString generateNotarySeal(Document document, PrivateKey privateKey) {
try {
byte[] documentBytes = document.toBytes();
byte[] signatureBytes = cryptex.signBytes(privateKey, documentBytes);
BinaryString notarySeal = new BinaryString(signatureBytes);
return notarySeal;
} catch (Exception e) {
RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to notarize the following document: " + document, e);
throw logger.throwing(exception);
}
}
static private DocumentCitation generateDocumentCitation(URI reference, NotarizedDocument extends Document> notarizedDocument) {
DocumentCitation citation = new DocumentCitation();
citation.citedDocument = reference;
citation.hashingAlgorithm = HASHING_ALGORITHM;
citation.documentHash = hashOfDocument(notarizedDocument, HASHING_ALGORITHM);
return citation;
}
static private void validateNotarizedDocument(NotarizedDocument extends Document> notarizedDocument, PublicKey publicKey, Map errors) {
int errorCount = errors.size(); // record it to see if it changes
Document document = notarizedDocument.document;
BinaryString notarySeal = notarizedDocument.notarySeal;
if (document == null) {
logger.error("The document is missing...");
errors.put("document.is.missing", notarizedDocument);
}
if (notarySeal == null) {
logger.error("The notary seal is missing...");
errors.put("notary.seal.is.missing", notarizedDocument);
}
if (errorCount == errors.size()) {
// no new errors, so parameters should be valid
validateDocument(document, errors);
validateNotarySeal(document, notarySeal, publicKey, errors);
}
}
static private void validateDocument(Document document, Map errors) {
if (document.accountTag == null) {
logger.error("The document account tag is missing...");
errors.put("document.account.tag.is.missing", document);
}
if (document.documentTag == null) {
logger.error("The document tag is missing...");
errors.put("document.tag.is.missing", document);
}
if (document.documentVersion == null) {
logger.error("The document version is missing...");
errors.put("document.version.is.missing", document);
}
if (document.documentType == null) {
logger.error("The document type is missing...");
errors.put("document.type.is.missing", document);
}
if (document.timestamp == null) {
logger.error("The document timestamp is missing...");
errors.put("document.timestamp.is.missing", document);
}
boolean firstVersion = document.documentVersion.equals(new VersionString(1));
if (!firstVersion && document.previousDocument == null) {
logger.error("The previous document citation is missing...");
errors.put("previous.document.citation.is.missing", document);
}
if (firstVersion && document.previousDocument != null) {
logger.error("The previous document citation should be null for the first version...");
errors.put("previous.document.citation.should.be.null", document);
}
boolean isCertificate = document instanceof Certificate;
if (!isCertificate && document.certificateCitation == null) {
logger.error("The notary certificate citation is missing...");
errors.put("notary.certificate.citation.is.missing", document);
}
if (isCertificate && !firstVersion && document.certificateCitation == null) {
logger.error("The notary certificate citation is missing...");
errors.put("notary.certificate.citation.is.missing", document);
}
if (isCertificate && firstVersion && document.certificateCitation != null) {
logger.error("The notary certificate citation should be null for the first certificate...");
errors.put("notary.certificate.citation.should.be.null", document);
}
}
static private void validateNotaryKey(NotaryKey notaryKey, Map errors) {
int errorCount = errors.size(); // record it to see if it changes
NotarizedDocument notaryCertificate = notaryKey.notaryCertificate;
PrivateKey privateKey = notaryKey.privateKey;
Certificate certificate = null;
if (notaryCertificate == null) {
logger.error("The notary certificate is missing...");
errors.put("notary.certificate.is.missing", notaryKey);
} else {
certificate = notaryCertificate.document;
PublicKey publicKey = certificate.publicKey;
validatePrivateKey(privateKey, publicKey, errors);
}
if (privateKey == null) {
logger.error("The private key is missing...");
errors.put("private.key.is.missing", notaryKey);
}
if (errorCount == errors.size()) {
// no new errors, so parameters should be valid
validateCertificate(certificate, errors);
}
}
static private void validateCertificate(Certificate certificate, Map errors) {
validateDocument(certificate, errors);
if (certificate.signingAlgorithm == null) {
logger.error("The notary certificate signing algorithm is missing...");
errors.put("notary.certificate.signing.algorithm.is.missing", certificate);
}
if (certificate.encryptionAlgorithm == null) {
logger.error("The notary certificate encryption algorithm is missing...");
errors.put("notary.certificate.encryption.algorithm.is.missing", certificate);
}
if (certificate.publicKey == null) {
logger.error("The notary certificate public key is missing...");
errors.put("notary.certificate.public.key.is.missing", certificate);
}
if (certificate.privateSlice == null) {
logger.error("The notary certificate private slice is missing...");
errors.put("notary.certificate.private.slice.is.missing", certificate);
}
}
static private void validatePrivateKey(PrivateKey privateKey, PublicKey publicKey, Map errors) {
String algorithm = privateKey.getAlgorithm();
if (!ENCRYPTION_ALGORITHM.startsWith(algorithm)) {
logger.error("The private key is of the wrong type...");
errors.put("private.key.is.wrong.type", algorithm);
}
byte[] randomBytes = RandomUtils.generateRandomBytes(16);
byte[] signatureBytes = cryptex.signBytes(privateKey, randomBytes);
if (!cryptex.bytesAreValid(publicKey, randomBytes, signatureBytes)) {
logger.error("The private key does not correspond to the public key...");
errors.put("private.key.not.valid", privateKey);
}
}
static private void validateNotarySeal(Document document, BinaryString notarySeal, PublicKey publicKey, Map errors) {
byte[] documentBytes = document.toBytes();
byte[] signatureBytes = notarySeal.toBytes();
if (!cryptex.bytesAreValid(publicKey, documentBytes, signatureBytes)) {
logger.error("The document signature is not valid...");
errors.put("document.is.not.valid", document);
errors.put("document.notary.seal.is.not.valid", notarySeal);
errors.put("document.public.key.does.not.match", publicKey);
}
}
static private void validateDocumentCitation(DocumentCitation documentCitation, NotarizedDocument extends Document> notarizedDocument, Map errors) {
String hashingAlgorithm = documentCitation.hashingAlgorithm;
BinaryString documentHash = hashOfDocument(notarizedDocument, hashingAlgorithm);
if (!Objects.equals(documentHash, documentCitation.documentHash)) {
logger.error("The document hash is not valid...");
errors.put("document.hash.is.not.valid", documentCitation);
errors.put("document.is.not.valid", notarizedDocument);
}
}
static private BinaryString hashOfDocument(NotarizedDocument extends Document> document, String algorithm) {
try {
byte[] documentBytes = document.toBytes();
MessageDigest hasher = MessageDigest.getInstance(algorithm);
byte[] hash = hasher.digest(documentBytes);
BinaryString hashBytes = new BinaryString(hash);
BinaryString documentHash = hashBytes;
return documentHash;
} catch (Exception e) {
RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to hash the following document: " + document, e);
throw logger.throwing(exception);
}
}
static private byte[] hashOfPassphrase(char[] passPhrase) {
CharBuffer charBuffer = null;
ByteBuffer byteBuffer = null;
byte[] passPhraseBytes = null;
try {
charBuffer = CharBuffer.wrap(passPhrase);
byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
passPhraseBytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
MessageDigest hasher = MessageDigest.getInstance(HASHING_ALGORITHM);
byte[] hashBytes = hasher.digest(passPhraseBytes);
return hashBytes;
} catch (Exception e) {
RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to convert a char[] to byte[].", e);
throw logger.throwing(exception);
} finally {
// null out all traces of the pass phrase
if (passPhrase != null) Arrays.fill(passPhrase, '\u0000');
if (charBuffer != null) Arrays.fill(charBuffer.array(), '\u0000');
if (byteBuffer != null) Arrays.fill(byteBuffer.array(), (byte) 0);
if (passPhraseBytes != null) Arrays.fill(passPhraseBytes, (byte) 0);
}
}
static private byte[] xorOfByteArrays(byte[] first, byte[] second) {
int length = first.length;
if (length != second.length) {
RuntimeException exception = new RuntimeException("The private key and the hashed passPhrase are different lengths: " + first.length + " and " + second.length);
throw logger.throwing(exception);
}
byte[] xor = new byte[length];
for (int i = 0; i < length; i++) {
xor[i] = (byte) (0xff & (first[i] ^ second[i]));
}
return xor;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy