001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org> 002// 003// SPDX-License-Identifier: Apache-2.0 004 005package org.pgpainless.sop; 006 007import java.io.IOException; 008import java.io.InputStream; 009import java.io.OutputStream; 010 011import org.bouncycastle.openpgp.PGPException; 012import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 013import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; 014import org.bouncycastle.util.io.Streams; 015import org.pgpainless.PGPainless; 016import org.pgpainless.algorithm.DocumentSignatureType; 017import org.pgpainless.algorithm.StreamEncoding; 018import org.pgpainless.encryption_signing.EncryptionOptions; 019import org.pgpainless.encryption_signing.EncryptionStream; 020import org.pgpainless.encryption_signing.ProducerOptions; 021import org.pgpainless.encryption_signing.SigningOptions; 022import org.pgpainless.exception.WrongPassphraseException; 023import org.pgpainless.key.protection.SecretKeyRingProtector; 024import org.pgpainless.util.Passphrase; 025import sop.util.ProxyOutputStream; 026import sop.Ready; 027import sop.enums.EncryptAs; 028import sop.exception.SOPGPException; 029import sop.operation.Encrypt; 030 031public class EncryptImpl implements Encrypt { 032 033 EncryptionOptions encryptionOptions = new EncryptionOptions(); 034 SigningOptions signingOptions = null; 035 036 private EncryptAs encryptAs = EncryptAs.Binary; 037 boolean armor = true; 038 039 @Override 040 public Encrypt noArmor() { 041 armor = false; 042 return this; 043 } 044 045 @Override 046 public Encrypt mode(EncryptAs mode) throws SOPGPException.UnsupportedOption { 047 this.encryptAs = mode; 048 return this; 049 } 050 051 @Override 052 public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.CertCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { 053 try { 054 PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); 055 if (keys.size() != 1) { 056 throw new SOPGPException.BadData(new AssertionError("Exactly one secret key at a time expected. Got " + keys.size())); 057 } 058 059 if (signingOptions == null) { 060 signingOptions = SigningOptions.get(); 061 } 062 try { 063 signingOptions.addInlineSignatures(SecretKeyRingProtector.unprotectedKeys(), keys, DocumentSignatureType.BINARY_DOCUMENT); 064 } catch (IllegalArgumentException e) { 065 throw new SOPGPException.CertCannotSign(); 066 } catch (WrongPassphraseException e) { 067 throw new SOPGPException.KeyIsProtected(); 068 } 069 } catch (IOException | PGPException e) { 070 throw new SOPGPException.BadData(e); 071 } 072 return this; 073 } 074 075 @Override 076 public Encrypt withPassword(String password) throws SOPGPException.PasswordNotHumanReadable, SOPGPException.UnsupportedOption { 077 encryptionOptions.addPassphrase(Passphrase.fromPassword(password)); 078 return this; 079 } 080 081 @Override 082 public Encrypt withCert(InputStream cert) throws SOPGPException.CertCannotEncrypt, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { 083 try { 084 PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing() 085 .keyRingCollection(cert, false) 086 .getPgpPublicKeyRingCollection(); 087 encryptionOptions.addRecipients(certificates); 088 } catch (IOException | PGPException e) { 089 throw new SOPGPException.BadData(e); 090 } 091 return this; 092 } 093 094 @Override 095 public Ready plaintext(InputStream plaintext) throws IOException { 096 ProducerOptions producerOptions = signingOptions != null ? 097 ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) : 098 ProducerOptions.encrypt(encryptionOptions); 099 producerOptions.setAsciiArmor(armor); 100 producerOptions.setEncoding(encryptAsToStreamEncoding(encryptAs)); 101 102 try { 103 ProxyOutputStream proxy = new ProxyOutputStream(); 104 EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() 105 .onOutputStream(proxy) 106 .withOptions(producerOptions); 107 108 return new Ready() { 109 @Override 110 public void writeTo(OutputStream outputStream) throws IOException { 111 proxy.replaceOutputStream(outputStream); 112 Streams.pipeAll(plaintext, encryptionStream); 113 encryptionStream.close(); 114 } 115 }; 116 } catch (PGPException e) { 117 throw new IOException(); 118 } 119 } 120 121 private static StreamEncoding encryptAsToStreamEncoding(EncryptAs encryptAs) { 122 switch (encryptAs) { 123 case Binary: 124 return StreamEncoding.BINARY; 125 case Text: 126 return StreamEncoding.TEXT; 127 case MIME: 128 return StreamEncoding.UTF8; 129 } 130 throw new IllegalArgumentException("Invalid value encountered: " + encryptAs); 131 } 132}