Skip to content

Commit

Permalink
some javadoc
Browse files Browse the repository at this point in the history
  • Loading branch information
profhenry committed Dec 31, 2023
1 parent fed885f commit b81ffbe
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* Defines the hash algorithms supported by SSH signature generation.
* <p>
* According to <a href="https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig">the SSH spec</a> the
* following are hash algorithms are supported
* following hash algorithms are supported
* <ul>
* <li>SHA-256</li>
* <li>SHA-512 (default)</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public class JcaSingingBackend implements SigningBackend<KeyPair> {
*/
private static final Logger LOGGER = LoggerFactory.getLogger(JcaSingingBackend.class);

@Override
public PublicKey extractPublicKey(KeyPair aKeyPair) {
return aKeyPair.getPublic();
}

@Override
public SigningResult signData(KeyPair aKeyPair, byte[] someDataToSign) throws SshSignatureException {
PrivateKey tPrivateKey = aKeyPair.getPrivate();
Expand All @@ -67,14 +72,14 @@ public SigningResult signData(KeyPair aKeyPair, byte[] someDataToSign) throws Ss
}

if ("DSA".equals(tPrivateKey.getAlgorithm())) {
return signDsa(tPrivateKey, tPublicKey, someDataToSign);
return signDsa(tPrivateKey, someDataToSign);
}
if ("RSA".equals(tPrivateKey.getAlgorithm())) {
return signRsa(tPrivateKey, tPublicKey, someDataToSign);
return signRsa(tPrivateKey, someDataToSign);
}
if ("Ed25519".equals(tPrivateKey.getAlgorithm())) {
// used by org.bouncycastle:bcprov in default configuration
return signEd25519(tPrivateKey, tPublicKey, someDataToSign);
return signEd25519(tPrivateKey, someDataToSign);
}
if ("EdDSA".equals(tPrivateKey.getAlgorithm())) {
// used by
Expand All @@ -84,7 +89,7 @@ public SigningResult signData(KeyPair aKeyPair, byte[] someDataToSign) throws Ss

// EdDSA is also used for Ed448 (at least when using JDK17)
// TODO i would love to prevent using Ed448 but i think this is not possible when compiling againt JDK8 :-/
return signEd25519(tPrivateKey, tPublicKey, someDataToSign);
return signEd25519(tPrivateKey, someDataToSign);
}

throw new SshSignatureException("Unsupported private key: "
Expand All @@ -107,8 +112,7 @@ private byte[] sign(PrivateKey aPrivateKey, String anAlgorithm, byte[] someDataT
}
}

private SigningResult signDsa(PrivateKey aPrivateKey, PublicKey aPublicKey, byte[] someDataToSign)
throws SshSignatureException {
private SigningResult signDsa(PrivateKey aPrivateKey, byte[] someDataToSign) throws SshSignatureException {

byte[] tSignedData = sign(aPrivateKey, "SHA1WithDSA", someDataToSign);
// DSA signatures consists of the two integers r and s.
Expand Down Expand Up @@ -142,7 +146,7 @@ private SigningResult signDsa(PrivateKey aPrivateKey, PublicKey aPublicKey, byte
LOGGER.debug("r+s: {}", HexUtil.bytesToHex(tRAndS));
LOGGER.debug(" < r >< s >");

return new SigningResult(SignatureAlgorithm.SSH_DSS, tRAndS, aPublicKey);
return new SigningResult(SignatureAlgorithm.SSH_DSS, tRAndS);
}

private void arrayCopyExact20Bytes(byte[] aSrc, int aScrOffset, byte[] aDst, int aDstOffset, int aLength) {
Expand All @@ -155,22 +159,20 @@ private void arrayCopyExact20Bytes(byte[] aSrc, int aScrOffset, byte[] aDst, int
}
}

private SigningResult signRsa(PrivateKey aPrivateKey, PublicKey aPublicKey, byte[] someDataToSign)
throws SshSignatureException {
private SigningResult signRsa(PrivateKey aPrivateKey, byte[] someDataToSign) throws SshSignatureException {

byte[] tSignedData = sign(aPrivateKey, "SHA512WithRSA", someDataToSign);
return new SigningResult(SignatureAlgorithm.RSA_SHA2_512, tSignedData, aPublicKey);
return new SigningResult(SignatureAlgorithm.RSA_SHA2_512, tSignedData);
}

private SigningResult signEd25519(PrivateKey aPrivateKey, PublicKey aPublicKey, byte[] someDataToSign)
throws SshSignatureException {
private SigningResult signEd25519(PrivateKey aPrivateKey, byte[] someDataToSign) throws SshSignatureException {

byte[] tSignedData;
if ("net.i2p.crypto.eddsa.EdDSAPrivateKey".equals(aPrivateKey.getClass().getName())) {
tSignedData = sign(aPrivateKey, "NONEwithEdDSA", someDataToSign);
} else {
tSignedData = sign(aPrivateKey, "ED25519", someDataToSign);
}
return new SigningResult(SignatureAlgorithm.SSH_ED25519, tSignedData, aPublicKey);
return new SigningResult(SignatureAlgorithm.SSH_ED25519, tSignedData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,49 @@
import java.nio.file.Path;

/**
* Data container for SSH signatures.
* <p>
* Contains
* <ul>
* <li>the raw binary signature data</li>
* <li>the used signature algorithm (as additional information)</li>
* </ul>
* <p>
* This class also provides methodes for getting the textual representation in PEM format.
* <p>
*
* @author profhenry
*/
public class SshSignature {

/**
* Label used in the PEM header and footer lines.
*/
private static final String PEM_LABEL = "SSH SIGNATURE";

// SSHSIG protocol states that content SHOULD wrap after 76 chars
// However the actual OpenSSH implementation wraps after 70 chars
/**
* Line length of the base64 encoded content.
* <p>
* <b>Please note:</b><br>
* RFC7468 states that content MUST wrap after 64 chars<br>
* SSHSIG protocol states that content SHOULD wrap after 76 chars<br>
* However the actual OpenSSH implementation wraps after 70 chars<br>
* <p>
* We are quite confused which value to use here. It seems that the OpenSSH implementation is able to read and
* verify signature files no matter what value is used, but we decided to stick to the value used by OpenSSH.
*/
private static final int LINE_LENGTH = 70;

/**
* The raw binary signature data.
*/
private final byte[] signatureData;

/**
* The used signature algotithm.
* <p>
* This is only provided for informational purposes as this is also encoded in the raw binary data.
*/
private final SignatureAlgorithm signatureAlgorithm;

public SshSignature(byte[] someSignatureData, SignatureAlgorithm aSignatureAlgorithm) {
Expand All @@ -50,17 +81,36 @@ public byte[] getSignatureData() {
return signatureData;
}

/**
* Returns the textural representation in PEM format.
* <p>
*
* @return string with signature in PEM format
*/
public String toPem() {
StringWriter tWriter = new StringWriter();
writeAsPem(tWriter);
return tWriter.toString();
}

/**
* Writes the textural representation in PEM format.
* <p>
*
* @param aWriter the writer
*/
public void writeAsPem(Writer aWriter) {
PemWriter tPemWriter = new PemWriter(aWriter, LINE_LENGTH);
tPemWriter.writeData(PEM_LABEL, signatureData);
}

/**
* Writes the signature to a file (in PEM format).
* <p>
*
* @param aPath the path to the file
* @throws IOException in case wirting the signature file failed
*/
public void writeAsPem(Path aPath) throws IOException {
try (BufferedWriter tWriter = Files.newBufferedWriter(aPath)) {
writeAsPem(tWriter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,30 @@
import de.profhenry.sshsig.core.util.HexUtil;

/**
* fdsfs for singing data using a SSH key
* Generator for SSH signatures.
* <p>
* https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.sshsig data since
* This implements the OpenSSH lightweight signature ability introduced with OpenSSH 8.1. It allows to sign messages
* using SSH keys according to the
* <a href="https://raw.githubusercontent.com/openssh/openssh-portable/master/PROTOCOL.sshsig">SSHSIG</a> protocol.
* <p>
* OpenSSH 8.1
* <b>Features:</b>
* <ul>
* <li>Content to be signed can be provided as string, byte array, file or as input stream</li>
* <li>Supported ssh key types: DSA, RSA, Ed25519</li>
* <li>Supported hashing algorithms: SHA-256 and SHA-512 (default SHA.512)</li>
* <li>Pluggable signing backend (default JCA)</li>
* </ul>
* <p>
* <b>Usage:</b>
*
* <pre>
* KeyPairGenerator tKeyPairGenerator = KeyPairGenerator.getInstance("RSA");
* KeyPair tKeyPair = tKeyPairGenerator.generateKeyPair();
* SshSignatureGenerator.create().generateSignature(tKeyPair, "namespace", "a message");
* </pre>
*
* @author profhenry
* @param <K> sdsds
* @param <K> the type for the key information
*/
public final class SshSignatureGenerator<K> {

Expand Down Expand Up @@ -250,7 +266,7 @@ private SshSignature generateSignature0(K aKey, String aNamespace, byte[] aHashe
tBuffer.appendInt(SIG_VERSION);

// public key
tBuffer.appendByteArray(publicKeyEncoder.encodePublicKey(tSigningResult.getPublicKey()));
tBuffer.appendByteArray(publicKeyEncoder.encodePublicKey(signingBackend.extractPublicKey(aKey)));
// namespace
tBuffer.appendString(aNamespace);
// reserved
Expand Down Expand Up @@ -294,6 +310,21 @@ private byte[] generateDataToSign(String aNamespace, byte[] aHashedMessage) {

// =================================================================================================================

/**
* Factory method for creating a SSH signature generator.
* <p>
* The created generator uses the following default configuration
* <ul>
* <li>JCA signing backend</li>
* <li>default SSH public key encoder</li>
* <li>SHA_512 for hashing messages</li>
* <li>8k buffer size when reading messages from an input stream</li>
* </ul>
* <p>
* The with... Methodes can be used for altering the default config.
*
* @return the SSH signature generator
*/
public static SshSignatureGenerator<KeyPair> create() {
return new SshSignatureGenerator<>(new JcaSingingBackend(),
new SshPublicKeyEncoder(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,40 @@
import de.profhenry.sshsig.core.SshSignatureException;

/**
* SPI for signing backends.
* <p>
* The singing backend is responsible for the actual signing. The signing can be done by this class itself or might be
* delegated to some external process (like when using an SSH agent). Depending on this the passed key information might
* look different. In the first case the key information must contain the private key in the second case the public key
* (or even just some kind of identifier) might be sufficient.
* <p>
*
* @author profhenry
* @param <K> the type for
* @param <K> the type for the key information
*/
public interface SigningBackend<K> {

static class SigningResult {
/**
* Data container for a signing result.
* <p>
*
* @author profhenry
*/
class SigningResult {

/**
* The used signature algorithm.
*/
private SignatureAlgorithm signatureAlgorithm;

/**
* The signed content.
*/
private byte[] signedContent;

private PublicKey publicKey;

public SigningResult(SignatureAlgorithm aSignatureAlgorithm, byte[] aSignedContent, PublicKey aPublicKey) {
public SigningResult(SignatureAlgorithm aSignatureAlgorithm, byte[] someSignedContent) {
signatureAlgorithm = aSignatureAlgorithm;
signedContent = aSignedContent;
publicKey = aPublicKey;
signedContent = someSignedContent;
}

public SignatureAlgorithm getSignatureAlgorithm() {
Expand All @@ -47,12 +64,27 @@ public SignatureAlgorithm getSignatureAlgorithm() {
public byte[] getSignedContent() {
return signedContent;
}

public PublicKey getPublicKey() {
return publicKey;
}

}

SigningResult signData(K aKey, byte[] someDataToSign) throws SshSignatureException;
/**
* Method for extracting the public key from the key information.
* <p>
* This is required because the public key gets encoded in the SSH signature.
* <p>
*
* @param someKeyInformation the key information
* @return the public key
*/
PublicKey extractPublicKey(K someKeyInformation);

/**
* Method for signing data using the provided key information.
* <p>
*
* @param someKeyInformation the key information
* @param someDataToSign the blob to be signed
* @return the signing result
* @throws SshSignatureException in case the signing failed
*/
SigningResult signData(K someKeyInformation, byte[] someDataToSign) throws SshSignatureException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public ApacheMinaSshAgentEngine(SshAgent anSshAgent) {
sshAgent = anSshAgent;
}

@Override
public PublicKey extractPublicKey(PublicKey aPublicKey) {
return aPublicKey;
}

@Override
public SigningResult signData(PublicKey aPublicKey, byte[] someDataToSign) throws SshSignatureException {
// 1) determine signature algorithm
Expand All @@ -53,7 +58,7 @@ public SigningResult signData(PublicKey aPublicKey, byte[] someDataToSign) throw
throw new SshSignatureException("", exc);
}

return new SigningResult(tSignatureAlgorithm, tSignedContent, aPublicKey);
return new SigningResult(tSignatureAlgorithm, tSignedContent);
}

protected SignatureAlgorithm determineSignatureAlgorithm(PublicKey aPublicKey) throws SshSignatureException {
Expand Down

0 comments on commit b81ffbe

Please sign in to comment.