diff --git a/src/main/java/org/jscep/message/PkiMessageDecoder.java b/src/main/java/org/jscep/message/PkiMessageDecoder.java index 9400f664..e6c307a5 100644 --- a/src/main/java/org/jscep/message/PkiMessageDecoder.java +++ b/src/main/java/org/jscep/message/PkiMessageDecoder.java @@ -218,8 +218,13 @@ public PkiMessage decode(final CMSSignedData pkiMessage) } catch (IOException e) { throw new MessageDecodingException(e); } + LOGGER.debug("Finished decoding pkiMessage"); - return new PkcsReq(transId, senderNonce, messageData); + if (messageType == MessageType.PKCS_REQ) { + return new PkcsReq(transId, senderNonce, messageData); + } + + return new RenewalReq(transId, senderNonce, messageData); } } } diff --git a/src/main/java/org/jscep/message/RenewalReq.java b/src/main/java/org/jscep/message/RenewalReq.java new file mode 100644 index 00000000..87781812 --- /dev/null +++ b/src/main/java/org/jscep/message/RenewalReq.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010 ThruPoint Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jscep.message; + +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.jscep.transaction.MessageType; +import org.jscep.transaction.Nonce; +import org.jscep.transaction.TransactionId; + +/** + * This class represents {@code PKCSReq} {@code pkiMessage}, which wraps a + * PKCS #10 {@code CertificationRequest} object. + */ +public class RenewalReq extends PkiRequest { + /** + * Creates a new {@code RenewalReq} instance. + * + * @param transId + * the transaction ID for this request. + * @param senderNonce + * the nonce for this request. + * @param messageData + * the {@code CertificationRequest} to use in enrollment. + */ + public RenewalReq(final TransactionId transId, final Nonce senderNonce, + final PKCS10CertificationRequest messageData) { + super(transId, MessageType.RENEWAL_REQ, senderNonce, messageData); + } +} diff --git a/src/main/java/org/jscep/server/ScepServlet.java b/src/main/java/org/jscep/server/ScepServlet.java index 2de8a8e0..92dc2699 100644 --- a/src/main/java/org/jscep/server/ScepServlet.java +++ b/src/main/java/org/jscep/server/ScepServlet.java @@ -286,12 +286,15 @@ public final void service(final HttpServletRequest req, LOGGER.error("Error executing GetCRL request", e); throw new ServletException(e); } - } else if (msgType == MessageType.PKCS_REQ) { + } else if (msgType == MessageType.PKCS_REQ || msgType == MessageType.RENEWAL_REQ) { final PKCS10CertificationRequest certReq = (PKCS10CertificationRequest) msgData; try { LOGGER.debug("Invoking doEnrol"); - List issued = doEnrol(certReq, reqCert, transId); + + List issued = + msgType == MessageType.PKCS_REQ ? doEnrol(certReq, reqCert, transId) : + doRenew(certReq, reqCert, transId); if (issued.isEmpty()) { certRep = new CertRep(transId, senderNonce, @@ -571,6 +574,33 @@ protected abstract List doEnrol( final X509Certificate sender, final TransactionId transId) throws Exception; + /** + * Renews a certificate into the PKI represented by this SCEP interface. If + * the request can be completed immediately, this method returns an + * appropriate certificate chain. If the request is pending, this method + * should return null or any empty list. + * + * @param certificationRequest + * the PKCS #10 CertificationRequest + * @param transId + * the transaction ID + * @param sender + * the sender of the certificate + * @return the certificate chain, if any + * @throws OperationFailureException + * if the operation cannot be completed + * @throws Exception + * if any problem occurs + */ + protected List doRenew( + final PKCS10CertificationRequest certificationRequest, + final X509Certificate sender, + final TransactionId transId) throws Exception { + + throw new UnsupportedOperationException("Override this function to support renewals." + + "This method will become abstract in the next major version update."); + } + /** * Returns the private key of the recipient entity represented by this SCEP * server. diff --git a/src/main/java/org/jscep/transaction/EnrollmentTransaction.java b/src/main/java/org/jscep/transaction/EnrollmentTransaction.java index 4790b39e..5d013b58 100644 --- a/src/main/java/org/jscep/transaction/EnrollmentTransaction.java +++ b/src/main/java/org/jscep/transaction/EnrollmentTransaction.java @@ -16,6 +16,7 @@ import org.jscep.message.PkiMessageDecoder; import org.jscep.message.PkiMessageEncoder; import org.jscep.message.PkiRequest; +import org.jscep.message.RenewalReq; import org.jscep.transport.Transport; import org.jscep.transport.request.PkiOperationRequest; import org.jscep.transport.response.PkiOperationResponseHandler; @@ -26,6 +27,7 @@ * This class represents a SCEP enrollment transaction. * * @see PkcsReq + * @see RenewalReq * @see GetCertInitial */ public final class EnrollmentTransaction extends Transaction { @@ -35,7 +37,7 @@ public final class EnrollmentTransaction extends Transaction { private final PkiRequest request; /** - * Constructs a new transaction for enrollment request. + * Constructs a new transaction for enrollment request with a PkcsReq message * * @param transport * the transport to use to send the transaction request. @@ -51,6 +53,28 @@ public final class EnrollmentTransaction extends Transaction { public EnrollmentTransaction(final Transport transport, final PkiMessageEncoder encoder, final PkiMessageDecoder decoder, final PKCS10CertificationRequest csr) throws TransactionException { + this(transport, encoder, decoder, csr, MessageType.PKCS_REQ); + } + + /** + * Constructs a new transaction for enrollment request. + * + * @param transport + * the transport to use to send the transaction request. + * @param encoder + * the encoder to encode the transaction request. + * @param decoder + * the decoder to decode the transaction response. + * @param csr + * the signing request to send. + * @param messageType + * type of message to send. + * @throws TransactionException + * if there is a problem creating the transaction ID. + */ + public EnrollmentTransaction(final Transport transport, + final PkiMessageEncoder encoder, final PkiMessageDecoder decoder, + final PKCS10CertificationRequest csr, final MessageType messageType) throws TransactionException { super(transport, encoder, decoder); try { this.transId = TransactionId.createTransactionId( @@ -58,7 +82,17 @@ public EnrollmentTransaction(final Transport transport, } catch (IOException e) { throw new TransactionException(e); } - this.request = new PkcsReq(transId, Nonce.nextNonce(), csr); + + switch(messageType) { + case PKCS_REQ: + this.request = new PkcsReq(transId, Nonce.nextNonce(), csr); + break; + case RENEWAL_REQ: + this.request = new RenewalReq(transId, Nonce.nextNonce(), csr); + break; + default: + throw new IllegalArgumentException("Message type must be PKCS_REQ or RENEWAL_REQ."); + } } /** diff --git a/src/main/java/org/jscep/transaction/MessageType.java b/src/main/java/org/jscep/transaction/MessageType.java index 4b9d9153..d2be125e 100644 --- a/src/main/java/org/jscep/transaction/MessageType.java +++ b/src/main/java/org/jscep/transaction/MessageType.java @@ -8,6 +8,10 @@ public enum MessageType { * Response to certificate or CRL request. */ CERT_REP(3), + /** + * PKCS #10 certificate request for renewal. + */ + RENEWAL_REQ(17), /** * PKCS #10 certificate request. */ diff --git a/src/test/java/org/jscep/server/ScepServletImpl.java b/src/test/java/org/jscep/server/ScepServletImpl.java index 99a6851e..6cf34a08 100644 --- a/src/test/java/org/jscep/server/ScepServletImpl.java +++ b/src/test/java/org/jscep/server/ScepServletImpl.java @@ -150,6 +150,33 @@ protected List doEnrol(PKCS10CertificationRequest csr, } } + @Override + protected List doRenew(PKCS10CertificationRequest csr, + X509Certificate sender, TransactionId transId + ) throws OperationFailureException { + try { + X500Name subject = X500Name.getInstance(csr.getSubject()); + LOGGER.debug(subject.toString()); + if (subject.equals(pollName)) { + return Collections.emptyList(); + } + authorizeRenewal(sender); + PublicKey pubKey = CertificationRequestUtils.getPublicKey(csr); + X509Certificate issued = generateCertificate(pubKey, subject, name, + getSerial()); + + LOGGER.debug("Issuing {}", issued); + CACHE.put( + new IssuerAndSerialNumber(name, issued.getSerialNumber()), + issued); + + return Collections.singletonList(issued); + } catch (Exception e) { + LOGGER.debug("Error in enrollment", e); + throw new OperationFailureException(FailInfo.badRequest); + } + } + private void authorizeRenewal(X509Certificate sender) throws CertificateEncodingException, OperationFailureException { X500Name issuer = X500Name.getInstance(sender.getIssuerX500Principal().getEncoded()); diff --git a/src/test/java/org/jscep/server/ScepServletTest.java b/src/test/java/org/jscep/server/ScepServletTest.java index f6afbc51..310fb4dd 100644 --- a/src/test/java/org/jscep/server/ScepServletTest.java +++ b/src/test/java/org/jscep/server/ScepServletTest.java @@ -331,7 +331,46 @@ public void testEnrollmentNotAuthorized() throws Exception { } @Test - public void testRenewal() throws Exception { + public void testRenewalThroughRenewalReq() throws Exception { + PKCS10CertificationRequest csr = getCsr(name, pubKey, priKey, + "password".toCharArray()); + + PkcsPkiEnvelopeEncoder envEncoder = new PkcsPkiEnvelopeEncoder( + getRecipient(), "DES"); + PkiMessageEncoder encoder = new PkiMessageEncoder(priKey, sender, + envEncoder); + + PkcsPkiEnvelopeDecoder envDecoder = new PkcsPkiEnvelopeDecoder(sender, + priKey); + PkiMessageDecoder decoder = new PkiMessageDecoder(getRecipient(), + envDecoder); + + Transport transport = transportFactory.forMethod(Method.POST, getURL()); + Transaction t = new EnrollmentTransaction(transport, encoder, decoder, + csr, MessageType.PKCS_REQ); + + State s = t.send(); + assertThat(s, is(State.CERT_ISSUED)); + Certificate[] certificateChain = t.getCertStore() + .getCertificates(null) + .toArray(new Certificate[0]); + X509Certificate prevCertificate = + (X509Certificate) certificateChain[certificateChain.length - 1]; + + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair(); + PrivateKey newPriKey = keyPair.getPrivate(); + PublicKey newPubKey = keyPair.getPublic(); + csr = getCsr(name, newPubKey, newPriKey, "badPassword".toCharArray()); + encoder = new PkiMessageEncoder(priKey, prevCertificate, envEncoder); + envDecoder = new PkcsPkiEnvelopeDecoder(prevCertificate, priKey); + decoder = new PkiMessageDecoder(getRecipient(), envDecoder); + t = new EnrollmentTransaction(transport, encoder, decoder, csr, MessageType.RENEWAL_REQ); + State renewalSate = t.send(); + assertThat(renewalSate, is(State.CERT_ISSUED)); + } + + @Test + public void testRenewalThroughPkcsReq() throws Exception { PKCS10CertificationRequest csr = getCsr(name, pubKey, priKey, "password".toCharArray());