Skip to content

Commit ec4b97c

Browse files
author
Hidde Lycklama
committed
Add basis for keypair generation
1 parent d0c191b commit ec4b97c

File tree

10 files changed

+226
-15
lines changed

10 files changed

+226
-15
lines changed

pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@
7070
<version>1.16.10</version>
7171
<scope>provided</scope>
7272
</dependency>
73+
74+
<dependency>
75+
<groupId>org.bouncycastle</groupId>
76+
<artifactId>bcpkix-jdk15on</artifactId>
77+
<version>1.55</version>
78+
</dependency>
7379
</dependencies>
7480

7581
<build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ch.eth.infsec.controllers;
2+
3+
import ch.eth.infsec.model.UserDetails;
4+
import ch.eth.infsec.services.PKIService;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.security.core.context.SecurityContextHolder;
7+
import org.springframework.stereotype.Controller;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
10+
@Controller
11+
@RequestMapping(path = "/certificate")
12+
public class CertificateController {
13+
14+
@Autowired
15+
PKIService pkiService;
16+
17+
@RequestMapping(path = "/issue")
18+
public String issue() {
19+
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
20+
pkiService.issueCertificate(userDetails.getUser());
21+
22+
return "issue";
23+
}
24+
25+
26+
}

src/main/java/ch/eth/infsec/controllers/HomeController.java

-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
import org.springframework.web.bind.annotation.RequestMapping;
1212
import org.springframework.web.bind.annotation.RequestMethod;
1313

14-
import java.security.Principal;
15-
1614
@Controller
1715
public class HomeController {
1816

src/main/java/ch/eth/infsec/model/User.java

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
public class User {
1313

1414
@Id
15+
@Getter @Setter
1516
private String uid;
1617

1718
@Getter @Setter
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package ch.eth.infsec.services;
2+
3+
import ch.eth.infsec.model.User;
4+
import org.bouncycastle.jcajce.provider.keystore.PKCS12;
5+
6+
public interface PKIService {
7+
8+
/**
9+
* Issues a new certificate for the given user.
10+
* @param user User object.
11+
* @return PKCS#12 keystore object
12+
*/
13+
public PKCS12 issueCertificate(User user);
14+
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package ch.eth.infsec.services;
2+
3+
import ch.eth.infsec.IMoviesApplication;
4+
import ch.eth.infsec.model.User;
5+
import org.bouncycastle.asn1.ASN1Sequence;
6+
import org.bouncycastle.asn1.DERBMPString;
7+
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
8+
import org.bouncycastle.asn1.x500.X500Name;
9+
import org.bouncycastle.asn1.x500.X500NameBuilder;
10+
import org.bouncycastle.asn1.x500.style.BCStyle;
11+
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
12+
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
13+
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
14+
import org.bouncycastle.cert.X509CertificateHolder;
15+
import org.bouncycastle.cert.X509v3CertificateBuilder;
16+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
17+
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
18+
import org.bouncycastle.crypto.util.PrivateKeyFactory;
19+
import org.bouncycastle.jcajce.PKCS12Key;
20+
import org.bouncycastle.jcajce.provider.keystore.PKCS12;
21+
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
22+
import org.bouncycastle.operator.ContentSigner;
23+
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
24+
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
25+
import org.bouncycastle.operator.OperatorCreationException;
26+
import org.bouncycastle.operator.bc.BcECContentSignerBuilder;
27+
import org.springframework.stereotype.Service;
28+
29+
30+
import java.io.*;
31+
import java.math.BigInteger;
32+
import java.security.*;
33+
import java.security.cert.*;
34+
import java.security.cert.Certificate;
35+
import java.util.Date;
36+
37+
@Service
38+
public class PKIServiceImpl implements PKIService {
39+
40+
final String caKeyStorePassword = "imovies";
41+
final String caKeyStoreKeyPassword = "imovies";
42+
final String caKeyStoreAlias = "imovieskeystore";
43+
44+
@Override
45+
public PKCS12 issueCertificate(User user) {
46+
KeyStore caKeyStore = caKeyStore();
47+
Certificate caCertificate = null;
48+
KeyPair caKeyPair = null;
49+
try {
50+
Key privateKey = caKeyStore.getKey(caKeyStoreAlias, caKeyStoreKeyPassword.toCharArray());
51+
assert privateKey instanceof PrivateKey;
52+
caCertificate = caKeyStore.getCertificate("Hidde Lycklama");
53+
caKeyPair = new KeyPair(caCertificate.getPublicKey(), (PrivateKey)privateKey);
54+
} catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
55+
throw new RuntimeException("Could not load key from keyStore", e);
56+
}
57+
58+
KeyPair clientKeyPair = generateKeyPair(user);
59+
Certificate clientCertificate = generateCertificate(user, clientKeyPair.getPublic(), caKeyPair);
60+
generatePKCS12(clientKeyPair, new Certificate[] { caCertificate, clientCertificate });
61+
62+
return null;
63+
64+
}
65+
66+
private Certificate generateCertificate(
67+
User user, PublicKey userKey, KeyPair caDetails) {
68+
69+
Date startDate = new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000);
70+
Date endDate = new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000);
71+
72+
73+
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
74+
authority(),
75+
BigInteger.ONE,
76+
startDate,
77+
endDate,
78+
subject(user),
79+
SubjectPublicKeyInfo.getInstance(userKey.getEncoded())
80+
);
81+
82+
X509CertificateHolder certificateHolder = builder.build(contentSigner(caDetails));
83+
84+
try {
85+
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
86+
} catch (CertificateException e) {
87+
throw new RuntimeException("Could not convert certificate", e);
88+
}
89+
}
90+
91+
private KeyPair generateKeyPair(User user) {
92+
try {
93+
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
94+
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
95+
keyGen.initialize(256, random);
96+
return keyGen.generateKeyPair();
97+
} catch (NoSuchAlgorithmException e) {
98+
throw new RuntimeException("Invalid algorithm used to generate keypair!", e);
99+
}
100+
}
101+
102+
private X500Name subject(User user) {
103+
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
104+
nameBuilder.addRDN(BCStyle.CN, user.getUid());
105+
nameBuilder.addRDN(BCStyle.EmailAddress, user.getEmail());
106+
nameBuilder.addRDN(BCStyle.O, "iMovies");
107+
nameBuilder.addRDN(BCStyle.OU, "Personal");
108+
return nameBuilder.build();
109+
}
110+
111+
/**
112+
* Generate the authority. Will load from file later.
113+
* @return authority data
114+
*/
115+
private X500Name authority() {
116+
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
117+
nameBuilder.addRDN(BCStyle.CN, "imovies.com");
118+
nameBuilder.addRDN(BCStyle.O, "iMovies");
119+
nameBuilder.addRDN(BCStyle.OU, "CA");
120+
return nameBuilder.build();
121+
}
122+
123+
private KeyStore caKeyStore() {
124+
// Load from keystore file
125+
try {
126+
InputStream is = PKIServiceImpl.class.getResourceAsStream("crypto/imovieskeystore.pfx");
127+
assert is != null;
128+
129+
KeyStore store = KeyStore.getInstance("PKCS12");
130+
store.load(is, caKeyStorePassword.toCharArray());
131+
132+
return store;
133+
} catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e) {
134+
throw new RuntimeException("Could not load CA keystore", e);
135+
}
136+
137+
}
138+
139+
private void generatePKCS12(KeyPair keyPair, Certificate[] certificates) {
140+
//
141+
PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)keyPair.getPrivate();
142+
143+
bagAttr.setBagAttribute(
144+
PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
145+
new DERBMPString("Client key"));
146+
bagAttr.setBagAttribute(
147+
PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
148+
new SubjectKeyIdentifier(keyPair.getPublic().getEncoded()));
149+
150+
try {
151+
KeyStore store = KeyStore.getInstance("PKCS12", "BC");
152+
store.load(null, null);
153+
store.setKeyEntry("Client key", keyPair.getPrivate(), null, certificates);
154+
155+
FileOutputStream fOut = new FileOutputStream("id.p12");
156+
store.store(fOut, null);
157+
} catch (KeyStoreException | NoSuchProviderException e) {
158+
throw new RuntimeException("Invalid provider BouncyCastle!", e);
159+
} catch (CertificateException | NoSuchAlgorithmException | IOException e) {
160+
throw new RuntimeException("Could not load null into keystore!", e);
161+
}
162+
163+
}
164+
165+
private ContentSigner contentSigner(KeyPair keyPair) {
166+
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
167+
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
168+
try {
169+
AsymmetricKeyParameter privateKeyAsymKeyParam = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
170+
return new BcECContentSignerBuilder(sigAlgId, digAlgId).build(privateKeyAsymKeyParam);
171+
} catch (IOException | OperatorCreationException e) {
172+
throw new RuntimeException("Invalid algorithm used to sign content!", e);
173+
}
174+
}
175+
176+
}
1.03 KB
Binary file not shown.

src/main/resources/public/css/bootstrap.min.css

-6
This file was deleted.

src/main/resources/public/js/bootstrap.min.js

-7
This file was deleted.

src/main/resources/templates/account.html

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ <h1>Manage account</h1>
3737
</div>
3838
<p><button type="submit" value="Submit">Submit</button> <button type="reset">Reset</button></p>
3939
</form>
40+
41+
<a class="btn btn-primary" th:href="@{/certificate/issue}">Download PCKS#12 certificate!</a>
4042
</div>
4143
</div>
4244

0 commit comments

Comments
 (0)