001/* 002 * Copyright 2007-2018 The jdeb developers. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.vafer.jdeb.signing; 018 019import java.io.ByteArrayInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.InputStreamReader; 023import java.io.OutputStream; 024import java.nio.charset.Charset; 025import java.security.GeneralSecurityException; 026import java.util.Iterator; 027 028import org.apache.commons.io.LineIterator; 029import org.bouncycastle.bcpg.ArmoredOutputStream; 030import org.bouncycastle.bcpg.BCPGOutputStream; 031import org.bouncycastle.openpgp.PGPException; 032import org.bouncycastle.openpgp.PGPPrivateKey; 033import org.bouncycastle.openpgp.PGPSecretKey; 034import org.bouncycastle.openpgp.PGPSecretKeyRing; 035import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 036import org.bouncycastle.openpgp.PGPSignature; 037import org.bouncycastle.openpgp.PGPSignatureGenerator; 038import org.bouncycastle.openpgp.PGPUtil; 039import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; 040import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; 041import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; 042import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; 043 044/** 045 * Signing with OpenPGP. 046 */ 047public class PGPSigner { 048 049 private static final byte[] EOL = "\n".getBytes(Charset.forName("UTF-8")); 050 051 private PGPSecretKey secretKey; 052 private PGPPrivateKey privateKey; 053 private int digest; 054 055 public PGPSigner(InputStream keyring, String keyId, String passphrase, int digest) throws IOException, PGPException { 056 secretKey = getSecretKey(keyring, keyId); 057 if(secretKey == null) 058 { 059 throw new PGPException(String.format("Specified key %s does not exist in key ring %s", keyId, keyring)); 060 } 061 privateKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passphrase.toCharArray())); 062 this.digest = digest; 063 } 064 065 /** 066 * Creates a clear sign signature over the input data. (Not detached) 067 * 068 * @param input the content to be signed 069 * @param output the output destination of the signature 070 */ 071 public void clearSign(String input, OutputStream output) throws IOException, PGPException, GeneralSecurityException { 072 clearSign(new ByteArrayInputStream(input.getBytes("UTF-8")), output); 073 } 074 075 /** 076 * Creates a clear sign signature over the input data. (Not detached) 077 * 078 * @param input the content to be signed 079 * @param output the output destination of the signature 080 */ 081 public void clearSign(InputStream input, OutputStream output) throws IOException, PGPException, GeneralSecurityException { 082 083 PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(new BcPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), digest)); 084 signatureGenerator.init(PGPSignature.CANONICAL_TEXT_DOCUMENT, privateKey); 085 086 ArmoredOutputStream armoredOutput = new ArmoredOutputStream(output); 087 armoredOutput.beginClearText(digest); 088 089 LineIterator iterator = new LineIterator(new InputStreamReader(input)); 090 091 while (iterator.hasNext()) { 092 String line = iterator.nextLine(); 093 094 // trailing spaces must be removed for signature calculation (see http://tools.ietf.org/html/rfc4880#section-7.1) 095 byte[] data = trim(line).getBytes("UTF-8"); 096 097 armoredOutput.write(data); 098 armoredOutput.write(EOL); 099 100 signatureGenerator.update(data); 101 if (iterator.hasNext()) { 102 signatureGenerator.update(EOL); 103 } 104 } 105 106 armoredOutput.endClearText(); 107 108 PGPSignature signature = signatureGenerator.generate(); 109 signature.encode(new BCPGOutputStream(armoredOutput)); 110 111 armoredOutput.close(); 112 } 113 114 /** 115 * Returns the secret key. 116 */ 117 public PGPSecretKey getSecretKey() 118 { 119 return secretKey; 120 } 121 122 /** 123 * Returns the private key. 124 */ 125 public PGPPrivateKey getPrivateKey() 126 { 127 return privateKey; 128 } 129 130 /** 131 * Returns the secret key matching the specified identifier. 132 * 133 * @param input the input stream containing the keyring collection 134 * @param keyId the 4 bytes identifier of the key 135 */ 136 private PGPSecretKey getSecretKey(InputStream input, String keyId) throws IOException, PGPException { 137 PGPSecretKeyRingCollection keyrings = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator()); 138 139 Iterator rIt = keyrings.getKeyRings(); 140 141 while (rIt.hasNext()) { 142 PGPSecretKeyRing kRing = (PGPSecretKeyRing) rIt.next(); 143 Iterator kIt = kRing.getSecretKeys(); 144 145 while (kIt.hasNext()) { 146 PGPSecretKey key = (PGPSecretKey) kIt.next(); 147 148 if (key.isSigningKey() && String.format("%08x", key.getKeyID() & 0xFFFFFFFFL).equals(keyId.toLowerCase())) { 149 return key; 150 } 151 } 152 } 153 154 return null; 155 } 156 157 /** 158 * Trim the trailing spaces. 159 * 160 * @param line 161 */ 162 private String trim(String line) { 163 char[] chars = line.toCharArray(); 164 int len = chars.length; 165 166 while (len > 0) { 167 if (!Character.isWhitespace(chars[len - 1])) { 168 break; 169 } 170 len--; 171 } 172 173 return line.substring(0, len); 174 } 175}