Skip to content

Commit 2818fa9

Browse files
committed
fix(letter): hash DER certificate in INI/HIA letters for EBICS 3.0
The letters printed the SHA-256 of the public key by default, but the INI/HIA requests always transmit the X.509 certificate. Under H005 the letter must carry the SHA-256 of the DER-encoded certificate (spec 4.4.1.2.3), which the bank verifies against the request — the public-key hash failed bank-side verification fixes #49
1 parent c09a1ae commit 2818fa9

9 files changed

Lines changed: 122 additions & 161 deletions

File tree

src/main/java/org/kopi/ebics/client/Bank.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,11 @@ public class Bank implements EbicsBank, Savable {
4242
* @param url the bank URL
4343
* @param name the bank name
4444
* @param hostId the bank host ID
45-
* @param useCertificate does the bank use certificates for exchange ?
4645
*/
47-
public Bank(URL url, String name, String hostId, boolean useCertificate) {
46+
public Bank(URL url, String name, String hostId) {
4847
this.url = url;
4948
this.name = name;
5049
this.hostId = hostId;
51-
this.useCertificate = useCertificate;
5250
needSave = true;
5351
}
5452

@@ -127,17 +125,6 @@ public String getName() {
127125
return name;
128126
}
129127

130-
@Override
131-
public boolean useCertificate() {
132-
return useCertificate;
133-
}
134-
135-
@Override
136-
public void setUseCertificate(boolean useCertificate) {
137-
this.useCertificate = useCertificate;
138-
needSave = true;
139-
}
140-
141128
@Override
142129
public void setBankKeys(RSAPublicKey e002Key, RSAPublicKey x002Key) {
143130
this.e002Key = e002Key;
@@ -172,12 +159,6 @@ public String getSaveName() {
172159
* @serial
173160
*/
174161
private final String hostId;
175-
176-
/**
177-
* Does the bank use certificates for signing/crypting ?
178-
* @serial
179-
*/
180-
private boolean useCertificate;
181162

182163
/**
183164
* The bank name

src/main/java/org/kopi/ebics/client/EbicsClient.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,10 @@ public void createUserDirectories(EbicsUser user) {
135135
* the bank name
136136
* @param hostId
137137
* the bank host ID
138-
* @param useCertificate
139-
* does the bank use certificates ?
140138
* @return the created ebics bank
141139
*/
142-
private Bank createBank(URL url, String name, String hostId, boolean useCertificate) {
143-
Bank bank = new Bank(url, name, hostId, useCertificate);
140+
private Bank createBank(URL url, String name, String hostId) {
141+
Bank bank = new Bank(url, name, hostId);
144142
banks.put(hostId, bank);
145143
return bank;
146144
}
@@ -180,8 +178,6 @@ private Partner createPartner(EbicsBank bank, String partnerId) {
180178
* the user country
181179
* @param organization
182180
* the user organization or company
183-
* @param useCertificates
184-
* does the bank use certificates ?
185181
* @param saveCertificates
186182
* save generated certificates?
187183
* @param passwordCallback
@@ -192,11 +188,11 @@ private Partner createPartner(EbicsBank bank, String partnerId) {
192188
*/
193189
public User createUser(URL url, String bankName, String hostId, String partnerId,
194190
String userId, String name, String email, String country, String organization,
195-
boolean useCertificates, boolean saveCertificates, PasswordCallback passwordCallback)
191+
boolean saveCertificates, PasswordCallback passwordCallback)
196192
throws Exception {
197193
log.info(messages.getString("user.create.info", userId));
198194

199-
Bank bank = createBank(url, bankName, hostId, useCertificates);
195+
Bank bank = createBank(url, bankName, hostId);
200196
Partner partner = createPartner(bank, partnerId);
201197
try {
202198
User user = new User(partner, userId, name, email, country, organization,
@@ -208,7 +204,7 @@ public User createUser(URL url, String bankName, String hostId, String partnerId
208204
configuration.getSerializationManager().serialize(bank);
209205
configuration.getSerializationManager().serialize(partner);
210206
configuration.getSerializationManager().serialize(user);
211-
createLetters(user, useCertificates);
207+
createLetters(user);
212208
users.put(userId, user);
213209
partners.put(partner.getPartnerId(), partner);
214210
banks.put(bank.getHostId(), bank);
@@ -221,9 +217,8 @@ public User createUser(URL url, String bankName, String hostId, String partnerId
221217
}
222218
}
223219

224-
private void createLetters(EbicsUser user, boolean useCertificates)
220+
private void createLetters(EbicsUser user)
225221
throws GeneralSecurityException, IOException, EbicsException {
226-
user.getPartner().getBank().setUseCertificate(useCertificates);
227222
LetterManager letterManager = configuration.getLetterManager();
228223
List<InitLetter> letters = List.of(letterManager.createA005Letter(user),
229224
letterManager.createE002Letter(user), letterManager.createX002Letter(user));
@@ -512,10 +507,9 @@ private User createUser(ConfigProperties properties, PasswordCallback pwdHandler
512507
String userEmail = properties.get("user.email");
513508
String userCountry = properties.get("user.country");
514509
String userOrg = properties.get("user.org");
515-
boolean useCertificates = false;
516510
boolean saveCertificates = true;
517511
return createUser(new URL(bankUrl), bankName, hostId, partnerId, userId, userName, userEmail,
518-
userCountry, userOrg, useCertificates, saveCertificates, pwdHandler);
512+
userCountry, userOrg, saveCertificates, pwdHandler);
519513
}
520514

521515
private static CommandLine parseArguments(Options options, String[] args)
@@ -640,7 +634,7 @@ public static void main(String[] args) throws Exception {
640634
}
641635

642636
if (cmd.hasOption("letters")) {
643-
client.createLetters(client.defaultUser, false);
637+
client.createLetters(client.defaultUser);
644638
}
645639

646640
if (hasOption(cmd, OrderType.INI)) {

src/main/java/org/kopi/ebics/client/ParameterizedEbicsClientLauncher.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ public static void main(String[] args) throws Exception {
9999
env("EBICS_USER_EMAIL", userId + "@example.invalid"),
100100
env("EBICS_USER_COUNTRY", countryCode),
101101
env("EBICS_USER_ORGANIZATION", "EBICS"),
102-
resolveUseCertificate(),
103102
true,
104103
passwordCallback
105104
);
@@ -224,15 +223,6 @@ private static Properties buildConfigurationProperties(
224223
return properties;
225224
}
226225

227-
private static boolean resolveUseCertificate() {
228-
String explicit = normalize(System.getenv("EBICS_USE_CERTIFICATE"));
229-
if (explicit != null) {
230-
return "true".equalsIgnoreCase(explicit);
231-
}
232-
String signatureVersion = env("EBICS_SIGNATURE_VERSION", "A005");
233-
return "A006".equalsIgnoreCase(signatureVersion);
234-
}
235-
236226
private static void ensureLoadedUserMatchesConfiguredEndpoint(
237227
User user,
238228
String configuredBankUrl,

src/main/java/org/kopi/ebics/interfaces/EbicsBank.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,6 @@ public interface EbicsBank extends Serializable {
3535
*/
3636
URL getURL();
3737

38-
/**
39-
*
40-
*/
41-
boolean useCertificate();
42-
43-
/**
44-
*
45-
*/
46-
void setUseCertificate(boolean useCertificate);
47-
4838
/**
4939
* Returns the encryption key digest you have obtained from the bank.
5040
* Ensure that nobody was able to modify the digest on its way from the bank to you.

src/main/java/org/kopi/ebics/letter/A005Letter.java

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,19 @@ public A005Letter(Locale locale) {
4545

4646
@Override
4747
public void create(EbicsUser user) throws GeneralSecurityException, IOException, EbicsException {
48-
if (user.getPartner().getBank().useCertificate()) {
49-
build(user.getPartner().getBank().getHostId(),
50-
user.getPartner().getBank().getName(),
51-
user.getUserId(),
52-
user.getName(),
53-
user.getPartner().getPartnerId(),
54-
getString("INILetter.version"),
55-
getString("INILetter.certificate"),
56-
Base64.encodeBase64(user.getA005Certificate(), true),
57-
getString("INILetter.digest"),
58-
getHash(user.getA005Certificate()));
59-
} else {
60-
build(user.getPartner().getBank().getHostId(),
61-
user.getPartner().getBank().getName(),
62-
user.getUserId(),
63-
user.getName(),
64-
user.getPartner().getPartnerId(),
65-
getString("INILetter.version"),
66-
getString("INILetter.certificate"),
67-
null,
68-
getString("INILetter.digest"),
69-
getHash(user.getA005PublicKey()));
70-
}
48+
// EBICS 3.0 (H005): the INI letter must carry the SHA-256 hash of the
49+
// DER-encoded signature certificate (spec ch. 4.4.1.2.3), matching the
50+
// X.509 certificate transmitted in the INI request.
51+
build(user.getPartner().getBank().getHostId(),
52+
user.getPartner().getBank().getName(),
53+
user.getUserId(),
54+
user.getName(),
55+
user.getPartner().getPartnerId(),
56+
getString("INILetter.version"),
57+
getString("INILetter.certificate"),
58+
Base64.encodeBase64(user.getA005Certificate(), true),
59+
getString("INILetter.digest"),
60+
getHash(user.getA005Certificate()));
7161
}
7262

7363
@Override

src/main/java/org/kopi/ebics/letter/AbstractInitLetter.java

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,13 @@
2323
import java.io.OutputStream;
2424
import java.io.PrintWriter;
2525
import java.io.Writer;
26-
import java.nio.charset.StandardCharsets;
2726
import java.security.GeneralSecurityException;
2827
import java.security.MessageDigest;
29-
import java.security.interfaces.RSAPublicKey;
3028
import java.text.SimpleDateFormat;
3129
import java.util.Date;
3230
import java.util.Locale;
3331

3432
import org.apache.commons.codec.binary.Hex;
35-
import org.kopi.ebics.exception.EbicsException;
3633
import org.kopi.ebics.interfaces.InitLetter;
3734
import org.kopi.ebics.messages.Messages;
3835

@@ -100,8 +97,8 @@ protected String getString(String key) {
10097
}
10198

10299
/**
103-
* Returns the certificate hash
104-
* @param certificate the certificate
100+
* Returns the SHA-256 hash of the DER-encoded certificate.
101+
* @param certificate the DER-encoded certificate
105102
* @return the certificate hash
106103
* @throws GeneralSecurityException
107104
*/
@@ -111,36 +108,6 @@ protected byte[] getHash(byte[] certificate) throws GeneralSecurityException {
111108
return format(hash256).getBytes();
112109
}
113110

114-
protected byte[] getHash(RSAPublicKey publicKey) throws EbicsException {
115-
String modulus;
116-
String exponent;
117-
String hash;
118-
byte[] digest;
119-
120-
exponent = Hex.encodeHexString(publicKey.getPublicExponent().toByteArray());
121-
modulus = Hex.encodeHexString(removeFirstByte(publicKey.getModulus().toByteArray()));
122-
hash = exponent + " " + modulus;
123-
124-
if (hash.charAt(0) == '0') {
125-
hash = hash.substring(1);
126-
}
127-
128-
try {
129-
digest = MessageDigest.getInstance("SHA-256", "BC").digest(hash.getBytes(
130-
StandardCharsets.US_ASCII));
131-
} catch (GeneralSecurityException e) {
132-
throw new EbicsException(e.getMessage());
133-
}
134-
135-
return format(new String(Hex.encodeHex(digest, false))).getBytes();
136-
}
137-
138-
private static byte[] removeFirstByte(byte[] byteArray) {
139-
byte[] b = new byte[byteArray.length - 1];
140-
System.arraycopy(byteArray, 1, b, 0, b.length);
141-
return b;
142-
}
143-
144111
/**
145112
* Formats a hash 256 input.
146113
* @param hash256 the hash input
@@ -215,9 +182,9 @@ public void build(String certTitle,
215182
out = new ByteArrayOutputStream();
216183
writer = new PrintWriter(out, true);
217184
buildTitle();
218-
buildHeader();
219-
if (certificate != null) {
220-
buildCertificate(certTitle, certificate);
185+
buildHeader();
186+
if (certificate != null) {
187+
buildCertificate(certTitle, certificate);
221188
}
222189
buildHash(hashTitle, hash);
223190
buildFooter();

src/main/java/org/kopi/ebics/letter/E002Letter.java

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,19 @@ public E002Letter(Locale locale) {
4545

4646
@Override
4747
public void create(EbicsUser user) throws GeneralSecurityException, IOException, EbicsException {
48-
if (user.getPartner().getBank().useCertificate()) {
49-
build(user.getPartner().getBank().getHostId(),
50-
user.getPartner().getBank().getName(),
51-
user.getUserId(),
52-
user.getName(),
53-
user.getPartner().getPartnerId(),
54-
getString("HIALetter.e002.version"),
55-
getString("HIALetter.e002.certificate"),
56-
Base64.encodeBase64(user.getE002Certificate(), true),
57-
getString("HIALetter.e002.digest"),
58-
getHash(user.getE002Certificate()));
59-
} else {
60-
build(user.getPartner().getBank().getHostId(),
61-
user.getPartner().getBank().getName(),
62-
user.getUserId(),
63-
user.getName(),
64-
user.getPartner().getPartnerId(),
65-
getString("HIALetter.e002.version"),
66-
getString("HIALetter.e002.certificate"),
67-
null,
68-
getString("HIALetter.e002.digest"),
69-
getHash(user.getE002PublicKey()));
70-
}
48+
// EBICS 3.0 (H005): the HIA letter must carry the SHA-256 hash of the
49+
// DER-encoded encryption certificate (spec ch. 4.4.1.2.3), matching the
50+
// X.509 certificate transmitted in the HIA request.
51+
build(user.getPartner().getBank().getHostId(),
52+
user.getPartner().getBank().getName(),
53+
user.getUserId(),
54+
user.getName(),
55+
user.getPartner().getPartnerId(),
56+
getString("HIALetter.e002.version"),
57+
getString("HIALetter.e002.certificate"),
58+
Base64.encodeBase64(user.getE002Certificate(), true),
59+
getString("HIALetter.e002.digest"),
60+
getHash(user.getE002Certificate()));
7161
}
7262

7363
@Override

src/main/java/org/kopi/ebics/letter/X002Letter.java

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,19 @@ public X002Letter(Locale locale) {
4545

4646
@Override
4747
public void create(EbicsUser user) throws GeneralSecurityException, IOException, EbicsException {
48-
if (user.getPartner().getBank().useCertificate()) {
49-
build(user.getPartner().getBank().getHostId(),
50-
user.getPartner().getBank().getName(),
51-
user.getUserId(),
52-
user.getName(),
53-
user.getPartner().getPartnerId(),
54-
getString("HIALetter.x002.version"),
55-
getString("HIALetter.x002.certificate"),
56-
Base64.encodeBase64(user.getX002Certificate(), true),
57-
getString("HIALetter.x002.digest"),
58-
getHash(user.getX002Certificate()));
59-
} else {
60-
build(user.getPartner().getBank().getHostId(),
61-
user.getPartner().getBank().getName(),
62-
user.getUserId(),
63-
user.getName(),
64-
user.getPartner().getPartnerId(),
65-
getString("HIALetter.x002.version"),
66-
getString("HIALetter.x002.certificate"),
67-
null,
68-
getString("HIALetter.x002.digest"),
69-
getHash(user.getX002PublicKey()));
70-
}
48+
// EBICS 3.0 (H005): the HIA letter must carry the SHA-256 hash of the
49+
// DER-encoded authentication certificate (spec ch. 4.4.1.2.3), matching
50+
// the X.509 certificate transmitted in the HIA request.
51+
build(user.getPartner().getBank().getHostId(),
52+
user.getPartner().getBank().getName(),
53+
user.getUserId(),
54+
user.getName(),
55+
user.getPartner().getPartnerId(),
56+
getString("HIALetter.x002.version"),
57+
getString("HIALetter.x002.certificate"),
58+
Base64.encodeBase64(user.getX002Certificate(), true),
59+
getString("HIALetter.x002.digest"),
60+
getHash(user.getX002Certificate()));
7161
}
7262

7363
@Override

0 commit comments

Comments
 (0)